Showing posts with label WCF. Show all posts
Showing posts with label WCF. Show all posts

Thursday, April 28, 2011

Create CRUD UI using ASP.NET 4.0, FormView, Dynamic Data, DomainDataSource, Entity Framework, WCF Domain Service, LINQ, and custom validation – Part I

This is one entry in a series of blog entries:

The title is a mouthful and thus the topic can be kind of overwhelming. With ASP.NET 4 there are so many different technology frameworks that you need to be familiar with. The problem that I have found is that most examples only use a one or two of these frameworks. In my world, I want to use them all and the examples are never as simple as the examples. In this blog entry, I will give a read world example of how to combine all these technologies. I find myself having to figure out how to do this over and over again. Hopefully, this will be of help to others.

This blog entry is NOT about how to create a Dynamic Data project in VS2010 and create an admin UI or something like that. This is about creating a page from scratch that uses this technology. To better understand the different scenarios that Dynamic Data can be used in and steps on how to extend it, see here.

Technology Overview

I am writing in the context of ASP.NET 4.0 and VS2010 (Visual Studio 2010). These technologies are relatively new in some cases and the api’s have changed since betas. I am only showing stuff that works in VS2010 (the final released version).

  • ASP.NET 4.0 – This is the version of ASP.NET that I am targeting.
  • FormView – This is the control that we will be using as a basis for the CRUD UI (User Interface).
  • Dynamic Data – This is the technology that allows you to use DynamicControl on your FormView instead of the standard ASP.NET Forms controls. BTW, you don’t have to have anything installed (not even the Dynamic Data files and templates). You get basic implementation without them, but with them you can customize and get a more full-featured set of tools. See here for a GREAT video on using Dynamic Data in “old” applications that don’t have all these cool technologies. For the most up to date resources, click here.
  • Entity Framework 4.0 – The standard with VS2010 for database to object mapping. Click here if you want to see how to use Dynamic Data with an ObjectDataSource and GridView.
  • WCF Domain Service – This is your middle tier where you would put your business logic, complex validation, etc. It provides a consist way to access your perform CRUD operations on your data. Abstraction layer before the Entity Framework.
  • LINQ – We will use LINQ to Entities in the WCF Domain Service to query database through the Entity Framework.
  • DomainDataSource – This is a control that works much like the ObjectDataSource or LinqDataSource or EntityDataSource except that it can connect to your WCF Domain Service from ASP.NET. Please note that there is also one for Silverlight and it has the same name so when you are Googling be sure to check what the context is. This is a good doc on using this control.

Getting Started

You can select most any type of web application project in VS2010 to get started. My instructions will be for the Dynamic Data project so that I don’t have to explain how to manually move over the Dynamic Data files. If you have an existing project and need to copy over the Dynamic Data files, click here for detailed instructions. Please note, I have copied some of these steps (and written others) from instructions I have read on MSDN, but have combined them together in one continuous instruction set. For example, most of the instruction are from: here and here.

  1. Open Visual Studio 2010 and create a new project of type ASP.NET Dynamic Data Domain Service Web Application. I’m calling my project MyDynamicDataExample.
  2. Optional - Create two Directories: Models and Services.
  3. Optional - Download and install the AdventureWorksLT database if you don’t already have it. The particular database is not really that important to understand the concepts. Notice I am using the Lite version since it is a bit simpler to follow, etc.
  4. Add a New Item… and select ADO.NET Entity Data Model. I called MyModel.edmx. We will generate from Database. Select the database you want to work with (add a new connection if it is not there). On the Choose Your Database Objects screen, select Product and ProductCategory. Keep everything else default.
  5. You must build your project so that the next step will work.
  6. Add (to Services folder) a New Item… and select Domain Service Class. I called mine AWDomainService.cs. Select the options as shown below:
    image
  7. Add (to Models folder) a New Item… and select Web Form using Master Page. Call it DDTest.aspx. Select the Site.master on the next screen.
  8. From the General or Data group of the Toolbox, add a DomainDataSource control to the page. If you have never used the DomainDataSource before you will need to add it to your toolbox. To add it to your toolbox. Click the Tools menu, and then click Choose Toolbox Items. In the Choose Toolbox Items dialog box, click the .NET Framework Components tab, select the DomainDataSource check box, and then click OK.
  9. Add a DomainDataSource to the page. Configure the datasource to point to GetProducts() method on the service you created. Enable Inserts, Updates and Deletes.
  10. Add a QueryExtender to the page. Set the TargetControlID to the id of the DomainDataSource (mine is called DomainDataSource1).
  11. Add a FormView (though a GridView or DetailsView could also be used). Check the Enable Dynamic Data Support checkbox. Please note it will not stay checked when you come back to it later (in some cases). Set the Data Source to the DomainDataSource you added earlier.
  12. Add a DynamicDataManager control to the page. Chose Register Controls… and add the FormView as shown below.
    image
    This registers the data-bound control and enables dynamic behavior for it.

  13. Open the code-behind. Add a Page_Init method and add the following line:

    protected void Page_Init(object sender, EventArgs e)
    {
        FormView1.EnableDynamicData(typeof(MyDynamicDataExample.Models.Product));
    }

  14. Go to the .aspx page again and go to the Source view. Find the QueryExtender you added earlier. Add the following line to it:

    <asp:QueryExtender ID="QueryExtender1" runat="server" TargetControlID="DomainDataSource1">
            <asp:DynamicRouteExpression ColumnName="ProductID" />
    </asp:QueryExtender>

    This will allow us to pass the ID that we want to edit via the url as a query string.

  15. Now we need to do some clean up. In each of the templates in the FormView remove the Text and Dynamic controls for: ProductCategoryReference, EntityState, and EntityKey. Also, remove the rowguid from the insert template.

  16. Let’s test to see if this works now. You will see that you get the YSOD (Yellow Screen Of Death) when you click the Edit button and then the Update button. You will get an EntityOperationException and the last method that was called was HandleValidationErrors. This means that something in the model failed.

  17. Create event handlers on the DomainDataSource for the Updated and Inserted events. They should look like this when you are done:

    protected void DomainDataSource1_Inserted(object sender, Microsoft.Web.UI.WebControls.DomainDataSourceStatusEventArgs e)
    {
        if (e.ChangeSetEntry.HasError)
        {
            foreach (var error in e.ChangeSetEntry.ValidationErrors)
            {
                AddValidationSummaryItem(error.Message);
            }
        }
    }

    protected void DomainDataSource1_Updated(object sender, Microsoft.Web.UI.WebControls.DomainDataSourceStatusEventArgs e)
    {
        if (e.ChangeSetEntry.HasError)
        {
            foreach (var error in e.ChangeSetEntry.ValidationErrors)
            {
                AddValidationSummaryItem(error.Message);
            }
        }
    }

  18. Next add this supporting method.

    public void AddValidationSummaryItem(string errorMessage)
    {
        var validator = new CustomValidator();
        validator.IsValid = false;
        validator.ErrorMessage = errorMessage;
        this.Validators.Add(validator);
    }

  19. Now put a breakpoint on the Updated event handler and debug your application. Do the same test again, you’ll see there is some error about the field: ThumbnailPhoto. Note that the error does not display on the page, but it doesn’t go to the YSOD either. Like any other validation exception that is NOT column specific, we need to use the ValidationSummary control to view this error.

  20. Drag a ValidationSummary control to your page. Re-run and you’ll see the exception. This does give little bit of database schema information. This may be considered by some to be bad. I’ll leave that up to you. If you don’t do this then you won’t get validation exceptions that are thrown from your domain service which is how you will implement your business logic. My experience shows that nothing too serious comes out of here once you have everything wired up correctly. If you don’t like that you’ll have to come up with another solution.

  21. Let’s quickly fix this by removing the ThumbNailPhoto field from each of the FormView templates. Re-run, and this time you won’t get any errors. We can do this because it is an optional field.

  22. Let’s add some validation to see how to do that. Go to the AWDomainService.cs and find the UpdateProduct method. Modify it so that it looks like this:


    public void UpdateProduct(Product currentProduct)
    {
        this.ObjectContext.Products.AttachAsModified(currentProduct, this.ChangeSet.GetOriginal(currentProduct));
        if (currentProduct.ListPrice < 2000)
            throw new ValidationException("List Price is too low.");
    }
    Re-run, change the List Price to less than 2000 and update and you’ll get the message shown in the ValidationSummary control.

  23. You can also use ValidationAttributes such as Required, Range, RegularExpression, etc in the Model to declaratively validate the data. To do this open your AWDomainServices.metadata.cs. Find the StandardCost property and modify it so that it looks like this:

    [Range(0, 200, ErrorMessage="{0} must be between 0 and 200.")]
    public decimal StandardCost { get; set; }

    Re-run and and change the standard cost to something greater than 200 if it isn’t already and Update. You’ll see it says StandardCost must be between 0 and 200. Notice it filled in the name of the field for us. It is the actual property name though.

  24. To display a user-friendly version of the property name you can add Display information as shown below.

    [Display(Name="Standard Cost")]
    [Range(0, 200, ErrorMessage="{0} must be between 0 and 200.")]
    public decimal StandardCost { get; set; }

    Re-run and you’ll see the same message but with the friendly name of the property.

  25. To Edit a particular record such as the Product with ID = 800, just go to the same url, but with DDTest.aspx?ProductID=800.

  26. If you try to run your application without specifying the specific test page you will get an error like this “There are no accessible tables. Make sure that at least one data model is registered in Global.asax and scaffolding is enabled or implement custom pages.”

    To fix that you just need to go to your Global.asax.cs and uncomment the line that start with DefaultModel.RegisterContext. Change it so that it looks like this:

    DefaultModel.RegisterContext(new DomainModelProvider(typeof(MyDynamicDataExample.Services.AWDomainService)), new ContextConfiguration() { ScaffoldAllTables = true});

    When you do this you’ll have some CRUD pages for each of the tables you included. This is separate from what we are doing here which is a custom page, but let’s fix the errors anyway. Another option would be to not have this default page and leave that line above commented. That way you won’t have all these admin page open to the world. Yes you can secure them, but they are not by default.

  27. Back on track, when you access the Default.aspx page you will get an error like this: The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.

    To fix this just go to the AWDomainService.cs and add a sort method to the default query methods which are GetProducts and GetProductCategories. Just make the methods look like these:

    public IQueryable<ProductCategory> GetProductCategories()
    {
        return this.ObjectContext.ProductCategories.OrderBy(o => o.Name);
    }

    public IQueryable<Product> GetProducts()
    {
        return this.ObjectContext.Products.OrderBy(o => o.Name);
    }

  28. Re-run. This time go to the Product link. The same validation we added before is also here. This is because the validation is written at the Model, not the UI. This is AWESOME in my opinion. I love it. This is why I love Dynamic Data.

I hope this has been beneficial to all.

The entire source for this example (minus the AdventureWorksLT database) can be downloaded from here.

Tuesday, April 19, 2011

How to change the display format of a field using MetaData

Let’s assume you have a Domain Service class that you are access from your ASP.NET Dynamic Data application (should work for Silverlight client as well (I think)) and that you have a table called Person. The Person table has a field / property / column that is called BirthDate. By default it is displayed as a date AND time in the GridView and the Edit and Detail Forms. The way to change the display format is in the MetaData for the Person table. In particular the MetaData for the property BirthDate. The basic MetaData class is generated for you when you create the Domain Service class.

The question is what Attribute do we use to communicate what we want. The answer is: DataFormatAttribute. Use it like you would a string format in most other places in the .NET framework. In the example below only the date (not the time) will always be shown.

[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode=true)]
public DateTime PerformedOnDateTimeUtc { get; set; }

If we remove or set the ApplyFormatInEditMode parameter to false (as shown below) then insert and edit will still show the time, but the gridview will still only show the date (no time).

[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode=false)]
public DateTime PerformedOnDateTimeUtc { get; set; }

You can use custom date formats, use on different datatypes, etc.

Monday, April 11, 2011

How to consume a SharePoint Web Service with WCF Client

SharePoint Web Services are great because they can be called from any computer that can connect to SharePoint, not just the server that is running SharePoint. This is nice for installations of SharePoint like MOSS 2007 or 2010, but it is critical for SharePoint Online (BPOS) or SharePoint 360 where you will NOT have access to the server that SharePoint runs on. The problem is they are not nearly as easy to work with as just using the SharePoint object model, and they don’t expose everything through the web services. This is a real bummer! It is still a very useful alternative for situations where you cannot use the SharePoint Object Model.

There are a few tricks for working with SharePoint Web Services. There are lots of ways you can access the web services. I prefer to use WCF to access the services so that I get cool syntax from XElement.

Here is a list of web services that are available. Let’s use the Webs web service. Below are the step by step (close anyway) to using the web service in Visual Studio 2010.

  1. Create a console (for simplicity) or any other project type in Visual Studio 2010. NOTE: This can be on your laptop that is NOT running SharePoint.
  2. Go to the docs for the Webs Web Service. Here you will see that the url is: http://<Site>/_vti_bin/Webs.asmx you need to replace <Site> with your hostname or dns name, etc.
  3. Add a Service Reference to the above url (after you replaced <Site> with your site info). I am naming my MyWebs, but you can call it whatever you like, just make the appropriate changes to the code I show later.
  4. If you require all users to be authenticated (don’t accept anonymous users) then you will need to change the WCF bindings that were automatically created in your app.config such that it will pass security information to the web service. To do this, just search for the url of the web service. That will take you to a XML tag called endpoint. In that same XML tag you will see an attribute called WebsSoap. In this same app.config do a search for WebsSoap. This will take you to a binding tag. In that binding tag you will see a security tag. That security tag should have a mode=”None” attribute. Change mode=”None” to mode=”TransportCredentialOnly”. Then just below that you will see a transport tag, change the clientCredentialType=”None” to clientCredentialType=”Ntlm” and change the proxyCredentialType=”None” to proxyCredentialType=”Ntlm”
  5. All SharePoint services are work on Site Collections or Sites (webs) so really the docs should say http://<Site>/<subsite>/_vti_bin/Webs.asmx because if you are dealing with a subsite you need to use that url. You don’t need to add a service reference for each site, you can change it in the code. Be sure to change the endpoint address line in the example below.
  6. Since we are using WCF, we get XElement which gives us LINQ syntax and objects from XML. How cool is that! When determining what is available as attributes it is easiest to just look in the debugger (use the visualizer).
  7. Now that we the results as objects we can do whatever we want with them.

NOTE: You can also do this with the old Web Service client, but you don’t have XElement, LINQ support, but changing the URL is as easy as just changing the url and the Credentials property is all you need to change the credentials. You also don’t really have to worry about the WCF app.config stuff, but in the end I still like WCF.

 

private static void GetWebs()
{
    using (var ws = new MyWebs.WebsSoapClient())
    {
        // pass the proper credentials. Comment/Uncomment the proper lines depending on your situation
        ws.ClientCredentials.Windows.ClientCredential = System.Net.CredentialCache.DefaultNetworkCredentials; // use current security context
        //ws.ClientCredentials.Windows.ClientCredential = new NetworkCredential("username", "password", "domain"); // use another Active Directory account
        //ws.ClientCredentials.Windows.ClientCredential = new NetworkCredential("username", "password"); // use account that is used for BPOS

        // change url to the site we want to work with
        ws.Endpoint.Address = new System.ServiceModel.EndpointAddress("
http://myhost/sites/SomeSiteHere/_vti_bin/Webs.asmx");

        // get all the webs at and below the specified site
        var results = ws.GetAllSubWebCollection();
               
        // use XElement and LINQ to get our results and create objects from the XML.
        var test = from r in results.Elements()
                    orderby r.Attribute("Url").Value
                    select new { Title = r.Attribute("Title").Value, Url = r.Attribute("Url").Value };

        // the results are just objects now, so do whatever you want with them.
        foreach (var item in test)
        {
            //Console.WriteLine(item.Url + " | " + item.Title);
            Console.WriteLine(item.Url);
        }

    }
}

Monday, May 17, 2010

Unit Testing Asynchronous calls in Visual Studio 2010

I am simply amazed how much effort I had to go through to figure out how to test asynchronous calls in Visual Studio 2010 (VS2010). In the end, I was able to figure it out with the help of some blogs that I read.

VS2010 ships with integrated Unit Testing which I would like to take advantage of. I am writing a Silverlight application that calls a Windows Workflow Foundation service that I implemented using the WCF Workflow Service Application. I really like it, but I want to be able to unit test the workflow service.

The only way I found to test a WCF Workflow Service Application that I could find was to add a Service Reference to my Unit Test project. This is good for me because that is how Silverlight will call it. The problem is that the WCF Workflow Service Application can’t be called synchronously. So, we have to call it Asynchronously. The problem is that the Unit Test framework used in VS2010 does not support Asynchronous calls in  a Unit Test. Well, it runs, but doesn’t wait for the response to the Async call, so the test is pretty worthless.

Now that you know what I am trying to do, here is what I found as solutions.

Option 1: Simulate Synchronous call using an Asynchronous call

Here is a class I created to simplify the process of make an asynchronous call appear to be synchronous.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MyApp.Tests
{
public class AsyncTest
{

// max number of milliseconds to wait for an asynchronous call
int timeout = -1;

public AsyncTest()
{
// if debugging, make it a much larger like infinity
if (System.Diagnostics.Debugger.IsAttached)
{
timeout = -1; // infinity (wait for ever)
}
else
{
timeout = 20 * 1000; // 10 seconds
}
}

public AsyncTest(int timeout)
{
this.timeout = timeout;
}

// we'll use this to make the current thread wait until our asynchrous call finishes
ManualResetEvent block = new ManualResetEvent(false);

// we'll use this to flag if/when our async call finishes
bool isAsyncDone = false;

public void Done()
{
isAsyncDone = true; // flag that we are done (do NOT do this after calling block.Set() this will cause race conditions!!!!)
block.Set(); // tell the calling / this thread that it can continue now.
}

public void Wait()
{
block.WaitOne(timeout, false); // wait until block.Set() is called or the timeout expires (which ever comes).
Assert.IsTrue(isAsyncDone, "Test took too long"); // if it took too long then report it to the test framework.
block.Reset(); // set the event to non-signaled before making the next asynchronous call.
}
}
}

Here is an example of how you would use it to create a method that acts like a synchronous method, but calls an asynchronous WCF Service.

public GetWorkflowStatusCompletedEventArgs GetWorkflowStatus(long requestID)
{
AsyncTest at = new AsyncTest();

GetWorkflowStatusCompletedEventArgs returnedArgs = null;

// setup our service reference and callback for when it is done
ServiceClient wf = new ServiceClient();
wf.GetWorkflowStatusCompleted += delegate(object sender, GetWorkflowStatusCompletedEventArgs e)
{
returnedArgs = e;
at.Done();
};


wf.GetWorkflowStatusAsync(requestID);
at.Wait();

return returnedArgs;
}

I created one of these methods for each of the asynchronous methods I wanted to test. In fact I created a helper class to hold them. Now, in my class that has all my tests in it, I just call the methods on this helper class which are synchronous. Now the test run properly.

For completeness, here is what the unit test (testmethod) would look like.

[TestMethod]
public void TestCanGetWorkflowStatusTwiceInARow()
{
var status = Helper.GetWorkflowStatus(1234);
Assert.AreEqual<long>(status.RequestID, requestData.RequestID, "The wrong request id was returned.");
Assert.IsTrue(status.RequestID > 0);
}
Now I can write a unit test just as easily as I do any other unit test. The synchronous / asynchronous issue is encapsulated in a helper class. I like it. Not much extra work either. Especially since each helper method I write is almost identical. It could be generated if desired (using CodeSmith, etc).
I wish I could take credit for all this, but I can’t. The solution / implementation is completely mine, but the underlying technique is borrowed. For more info on those links, see here:

Option 2: Use the Silverlight Unit Test Application

I think this method is a reasonable approach, but for testing a WCF Service, it seems a bit unnatural to me. I like Option 1 better because I want my test results to be managed in VS2010. If nothing else other than no browser opens and also that you can block check-in of code if tests fail. The integrated Unit testing just seems a bit more integrated with VS2010.
I do think the Silverlight Unit Test Application is a great testing technology. However, I think it is best and most natural for testing Silverlight applications, not the web services they call.
There are lots of good blogs on the subject, so I won’t repeat it here. Here are some of the blogs that I found particularly useful when I went down this road.

Tuesday, May 4, 2010

Getting Current User when using WCF and Silverlight

First off, when you start to create a WCF Service in Visual Studio 2010 or 2008 for that matter, you can choose WCF Service, but if you are using Silverlight as the client, you do NOT want to select this. You want to select, Silverlight-enabled WCF Service. If you don’t or didn’t you can follow the instructions here to make sure a few things are in place and then you will be in the same position as if you had selected the Silverlight-enabled WCF Service.

All I want to do is get the username of the user that is using my Silverlight application. Note, this also opens the door to ASP.NET roles.

Alot of what I read said that if I mess with my app.config and turn on transport or message security then I can get the user if I go to System.ServiceModel.ServiceSecurityContext.Current. Well, maybe that was for a Self-Hosted WCF service or some other scenario, but I could not get it to work in my tests with Silverlight with IIS hosted WCF Service. I think my biggest difficulty with these docs were that all the configuration tags that I expected to see in the web.config (they had an app.config) were not there, but yet I had a working (without security) WCF Service.

I had to assume it uses some defaults. I figured out that I was right. If you read A Developer's Introduction to Windows Communication Foundation 4 you will understand much better. It is a fairly lengthy read, but well worth it. There is actually a section on Workflow Foundation 4, but the first part of the article is most excellent in describing the defaults and how they work. For instance search it for ProtocolMapping to see that the defaults include basicHttpBinding, netTcpBinding, netNamedPipeBinding, and netMsmqBinding. They are defined in the Machine.config. WCF 4 also support inheritance / merging of configuration items. Very cool.

I am using an IIS hosted WCF service. I want to use Windows Authentication for authentication. Nothing fancy. What I found works well and quite easily is ensure the following things are in specified and in synch with each other. They must all agree!

Web Server
  • IIS has anonymous access disabled (not enabled).
  • IIS has Integrated Windows authentication enabled.
  • If you are using Visual Studio 2010 and using the built-in dev server, the default settings are fine. I did NOT have to check the NTLM Authentication checkbox.
Web.config
  • This is needed to have ASP.NET be able to get security info as well.
<system.web>
<authentication mode="Windows"/>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
  • Make sure you have aspnet compatability enabled as follows:
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true"  aspNetCompatibilityEnabled="true"/>
Your Service Class
  • Make sure to add the following above your class for your WCF Service. You can also use Required, instead of of Allowed
    [AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]

And now the moment of glory. You can now get the user just like you would in ASP.NET.

System.Web.HttpContext.Current.User.Identity.Name

References: A Developer's Introduction to Windows Communication Foundation 4

Tuesday, April 13, 2010

Using RIA Services / WCF with multiple host headers

If you are using RIA Services or just plain old WCF you and you have more than one url that you use to access your website and use IIS 6 you will need to modify your web.config file.

In my example, I access the same IIS web site using two different host headers because I have a small web farm and I want to be able to check each server in the farm after a deployment, not just use the load balanced url.

This means that I have two urls, one for load balancing that everyone uses, and the one I use for testing to make sure a particular server in the farm is working.

In my case, I have http://myapp:8888 which everyone uses, and http://myapp:18888 I use to hit server one, http://myapp:28888 to hit server two, etc.

Here is the change I had to make to my web.config on the first server.

<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" >
<baseAddressPrefixFilters>
<add prefix="http://myapp:8888" />
<add prefix="http://myapp:18888" />
</baseAddressPrefixFilters>
</serviceHostingEnvironment>
</system.serviceModel>

If you don’t do this, you may get errors about anonymous, security, bindings, communication, etc. This tends to fix a lot of these issues.

This is one of the better references I have found for getting RIA Services and WCF running under .Net 3.x, IIS 6, etc.