The .NET Endpoint

Blog by the .NET and AppFabric teams about WCF and WF development, deployment, and management

Updating State in WCF WebHttp Services

Updating State in WCF WebHttp Services

  • Comments 1

This is part three of a twelve part series that introduces the features of WCF WebHttp Services in .NET 4.  In this post we will cover:

  • Creating operations for updating server state with the [WebInvoke] attribute
  • Persisting changes with the ADO.NET Entity Framework in a web service scenario

Over the course of this blog post series, we are building a web service called TeamTask.  TeamTask allows a team to track tasks assigned to members of the team.  Because the code in a given blog post builds upon the code from the previous posts, the posts are intended to be read in-order.

Downloading the TeamTask Code

At the end of this blog post, you’ll find a link that will allow you to download the code for the current TeamTask Service as a compressed file.  After extracting, you’ll find that it contains “Before” and “After” versions of the TeamTask solution.  If you would like to follow along with the steps outlined in this post, download the code and open the "Before" solution in Visual Studio 2010.  If you aren’t sure about a step, refer to the “After” version of the TeamTask solution.

Note:  If you try running the sample code and see a Visual Studio Project Sample Loading Error that begins with “Assembly could not be loaded and will be ignored…”, see here for troubleshooting.

Getting Visual Studio 2010

To follow along with this blog post series, you will need to have Microsoft Visual Studio 2010 and the full .NET 4 Framework installed on your machine.  (The client profile of the .NET 4 Framework is not sufficient.)  At the time of this posting, the Microsoft Visual Studio 2010 Ultimate Beta 2 is available for free download and there are numerous resources available regarding how to download and install, including this Channel 9 video.

 

Step 1: Adding an Operation to Update Tasks

In part one of this blog post series, we used the [WebGet] attribute to indicate that a given method was a service operation.  By specifying a UriTemplate value on the [WebGet] attribute, we further indicated that the given service operation should handle all HTTP GET requests when the request URI matches the UriTemplate.  However, we didn’t mention in part one that there is a second attribute used to specify service operations: the [WebInvoke] attribute.

The [WebInvoke] attribute is just like the [WebGet] attribute, except that with the [WebInvoke] attribute we are able to specify the HTTP method that the service operation should handle.  With the [WebGet] attribute, the HTTP method is always implicitly an HTTP GET.  Therefore, we’ll use the [WebInvoke] attribute to create a service operation for updating a given task.

  1. If you haven't already done so, download and open the “Before” solution of the code attached to this blog post.

  2. Open the TeamTaskService.cs file in the code editor and copy the code below into the TeamTaskService class.  You’ll need to add “using System.Data.Objects;” to the code file:

        [Description("Allows the details of a single task to be updated.")] 
        [WebInvoke(UriTemplate = "Tasks/{id}", Method = "PUT")] 
        public Task UpdateTask(string id, Task task) 
        { 
            // The id from the URI determines the id of the task 
           
    int parsedId = int.Parse(id);           
            task.Id = parsedId; 
                
            using (TeamTaskObjectContext objectContext =
                      new TeamTaskObjectContext()) 
            {  
                objectContext.AttachTask(task);  // We’ll need to implement this!
                objectContext.SaveChanges();
                // Because we allow partial updates, we need to refresh from the dB 
               
    objectContext.Refresh(RefreshMode.StoreWins, task);   
           


            return task; 
        }

    Notice that we used the [WebInvoke] attribute and specified the HTTP method to be “PUT”.  Also notice that we included a [Description] attribute for the automatic help page as we did in part two of this blog post series.

    The UpdateTask() method has two input parameters: id and task.  The id parameter value will  come from the URI of the request because of the matching {id} variable in the UriTemplate.  The task parameter is not mapped to any variables in the UriTemplate, so its value will come from the body of the request.

    In the implementation of the UpdateTask() method we need to parse the id parameter because it is of type string, but task id’s are of type int.  Obviously, this call to int.Parse() could throw an exception, but for now we’ll simply let the exception bubble up.  In part five of this blog post series we’ll add proper exception handling to ensure that a more appropriate HTTP response is returned if the parsing fails.

    One final subtlety to explain is the call to the Refresh() method, which will update the task instance with the current values from the database.  It might seem odd that we have to make this call given that we just persisted the same task instance to the database.  However, we are doing this because we will allow clients to make partial updates.  The task supplied in the request may only have values for the properties that are to be updated.  However, the task provided in the response should have all of the properties of the task, so we must get these property values from the database.  A custom stored procedure could be used to optimize this, but we won’t explore that option here.

  3. If we build the TeamTask.Service project as it is now, it will fail because the TeamTaskObjectContext doesn’t contain a definition for the AttachTask() method.  We’ll implement the AttachTask() method in the next step.

Step 2: Creating a mechanism for Attaching Tasks

As you may recall from part one of this blog post series, we are using the ADO.NET Entity Framework to move data between CLR instances and the database.  The ADO.NET Entity Framework uses an ObjectContext to cache the CLR instances locally and track changes made to the them.  To persist these local changes to the database, a developer calls the SaveChanges() method on the ObjectContext. 

However, with a web service like the TeamTask service, the instances that need to be persisted to the database usually aren’t being tracked by the ObjectContext because they’ve just been deserialized from the HTTP request.  Therefore, we need to inform the ObjectContext of the instances that we want to persist to the database and indicate which properties on the instances have new values. 

To do the work of informing the ObjectContext of our updated task, we’ll add an AttachTask() method to our custom ObjectContext, the TeamTaskObjectContext class.   We’ll also add a GetModifiedProperties() method to the Task class that will be responsible for indicating which properties on the task have new values.

  1. Open the TeamTaskObjectContext.cs file from the Model folder of the TeamTask.Service project in the code editor and copy the code below into the TeamTaskObjectContext class:

        public void AttachTask(Task task)
        {
            this.Tasks.Attach(task);
            ObjectStateEntry entry =
                this.ObjectStateManager.GetObjectStateEntry(task);
            foreach (string propertyName in task.GetModifiedProperties())
            {
                entry.SetModifiedProperty(propertyName);
            }
        }

     

    The AttachTask() method uses an ADO.NET Entity Framework class called the ObjectStateManager to get an ObjectStateEntry for the task, and then sets the modified properties on the entry.  The list of modified properties is supplied by the task itself in the GetModifiedProperties() method, which we’ll need to implement next.

  2. Open the Task.cs file in the code editor and copy the code below into the Task class.  You’ll need to add “using System.Collections.Generic;” to the code file:

        internal IEnumerable<string> GetModifiedProperties()
        {
            // Create the list with the required properties
           
    List<string> modifiedProperties = new List<string>() { "TaskStatusCode"
                                                                                         "OwnerUserName",
                                                                                         "Title" };
            // Add the optional properties 
           
    if (!string.IsNullOrWhiteSpace(this.Description)) 
            { 
                modifiedProperties.Add("Description"); 
            } 
            if (this.CompletedOn.HasValue) 
            { 
                modifiedProperties.Add("CompletedOn"); 
            } 
            return modifiedProperties; 
        }

    The GetModifiedProperties() simply returns a list of property names.  Notice that the Id and CreatedOn properties of the Task class will never be included in this list, indicating that these properties can never be updated.  We obviously don’t want to update the Id value of a task since we are using it for identity purposes.  A user might try to update the CreatedOn value of a give task by sending an HTTP PUT request with CreatedOn value supplied, but the value will always be ignored.

Helpful Tip: Web services like the TeamTask service are almost always a tier of an N-Tier application. If your interested in learning more about building N-Tier applications with WCF and the ADO .NET Entity Framework in .NET 4, Daniel Simmons from the Entity Framework Team has written some informative MSDN Magazine articles here and here.

 

Step 3: Updating Task Status with the HttpClient

Now that we’ve implemented the UpdateTask() operation, lets write some client code to update the status of one of the tasks.  We already covered the basics of using the HttpClient in part two of this blog post series, so we’ll go through the client code quickly.

  1. Open the Program.cs file of the TeamTask.Client project in the code editor and copy the following static method into the Program class:

        static Task GetTask(HttpClient client, int id) 
        { 
            Console.WriteLine("Getting task '{0}':", id); 
            using (HttpResponseMessage response = client.Get("Tasks")) 
            { 
                response.EnsureStatusIsSuccessful(); 
                return response.Content.ReadAsDataContract<List<Task>>() 
                                       .FirstOrDefault(task => task.Id == id); 
            } 
        }

    The static GetTask() method is very similar to the client code we wrote in part two.  The GetTask() method uses the ReadAsDataContract() extension method to get a typed list of tasks from the TeamTask service response and then returns a task from the the list with a given id.

  2. Also copy the following static method into the Program class:

        static Task UpdateTask(HttpClient client, Task task) 
        { 
            Console.WriteLine("Updating task '{0}':", task.Id); 
            Console.WriteLine(); 

            string updateUri = "Tasks/" + task.Id.ToString(); 
            HttpContent content = HttpContentExtensions.CreateDataContract(task); 
                    
            using (HttpResponseMessage response = client.Put(updateUri, content)) 
            { 
                response.EnsureStatusIsSuccessful(); 
                return response.Content.ReadAsDataContract<Task>(); 
            } 
        }

    The static UpdateTask() method is slightly more interesting than the GetTask() method since we have to provide a message body with the request.  The Put() extension method for the HttpClient takes both a URI argument and a content argument.  The URI includes the id of the task to be updated and the content is just a task instance that has been serialized using the static CreateDataContract() method.

  3. Copy this last static method into the Program class so that we can write out the state of a task to the console:

        static void WriteOutTask(Task task) 
        { 
            Console.WriteLine("  Id:      {0}", task.Id); 
            Console.WriteLine("  Title:   {0}", task.Title); 
            Console.WriteLine("  Status:  {0}", task.Status); 
            Console.WriteLine("  Created: {0}", task.CreatedOn.ToShortDateString()); 
            Console.WriteLine(); 
        }

  4. Now we’ll implement the Main() method to retrieve a task, update the status of the task, and then retrieve the task again to verify that the change was persisted to the database.  Replace any code within the Main() method with the following code:

        using (HttpClient client = new HttpClient("http://localhost:8080/TeamTask/"))
        { 
            // Getting task 1 
           
    int taskId = 1
            Task task1 = GetTask(client, taskId); 
            WriteOutTask(task1); 

            // Update task 1 
           
    task1.Status = TaskStatus.InProgress; 
            task1.CreatedOn = new DateTime(2009, 12, 1); 
            Task task1Updated = UpdateTask(client, task1); 
            WriteOutTask(task1Updated); 

            // Get task 1 again to see the updated status 
           
    Task task1Again = GetTask(client, taskId); 
            WriteOutTask(task1Again); 

            Console.ReadKey(); 

            // Return the dB to its original state 
           
    task1.Status = TaskStatus.Completed; 
            UpdateTask(client, task1); 
         }

    Notice that when we update the task1 status, we are also updating the task1 creation date.  However, you’ll recall that in the GetModifiedProperties() method on the Task class, we never set the CreatedOn property as modified, so we shouldn’t see the new creation date in the response from the update or when we retrieve the task a second time. 

  5. Start without debugging (Ctrl+F5) to get the TeamTask service running and then start the client by right clicking on the TeamTask.Client project in the “Solution Explorer” window and selecting “Debug”—>”Start New Instance”.  The console should contain the following output:

    ClientOutputInConsole

    Notice that the task status is updated from “Completed” to “InProgress”, but that the creation date remains unchanged.

Next Steps: Automatic and Explicit Format Selection in WCF WebHttp Services

Our TeamTask service development is coming along nicely.  The TeamTask service now supports querying for tasks, retrieving users, and partial updates of tasks.  We also have a client that can programmatically access the service.

However, you may have noticed that all of the responses we’ve received from the TeamTask service have been in XML.  In part four of this blog post series, we’ll see how easy it is to also generate JSON responses when we take a look at automatic and explicit format selection in WCF WebHttp Services.

Randall Tombaugh
Developer, WCF WebHttp Services

Attachment: Post3-UpdatingServerState.zip
  • I am trying the same thing given here but with my own entities and entity model.

    Now following method gives error "Bad Request" when HttpClientObject.Put("","") is called.......

    private static Role UpdateRole(HttpClient hc, Role role1)

           {

               Console.WriteLine("Updating Role {0} : ",     role1.RoleID);

               Console.WriteLine();

               string updateURI = "Roles/" + role1.RoleID.ToString();

               HttpContent content = HttpContentExtensions.CreateDataContract(role1);

               using (HttpResponseMessage response = hc.Put(updateURI, content))

               {

                   response.EnsureStatusIsSuccessful();

                   return response.Content.ReadAsDataContract<Role>();

               }

           }

    Here is the my service implementation for the same.......

    [WebInvoke(UriTemplate = "Roles/{roleID}", Method = "PUT")]

           public Role UpdateRole(string roleID, Role role)

           {

               int roleIDToUpdate = Convert.ToInt32(roleID);

               role.RoleID = roleIDToUpdate;

               using (PronerveObjectContext poc = new PronerveObjectContext())

               {

                   poc.AttachRoles(role);

                   poc.SaveChanges();

                   poc.Refresh(RefreshMode.StoreWins, role);

               }

               return role;

           }

    Can anyone please help me out with this issue......

    Thanks in advance....

    Nikhil Thaker

Page 1 of 1 (1 items)