Posts Tagged EF5

Using the WCF Web HTTP Programming Model with Entity Framework 5

Build a IIS-hosted WCF Service using the WCF Web HTTP Programming Model. Use basic HTTP Methods with the WCF Service to perform CRUD operations on a SQL Server database using a Data Access Layer, built with Entity Framework 5 and the Database First Development Model.

You can download a complete copy of this Post’s source code from DropBox.

Introduction

In the two previous Posts, we used the new Entity Framework 5 to create a Data Access Layer, using both the Code First and Database First Development Models. In this Post, we will create a Windows Communication Foundation (WCF) Service. The service will sit between the client application and our previous Post’s Data Access Layer (DAL), built with an ADO.NET Entity Data Model (EDM). Using the WCF Web HTTP Programming Model, we will expose the WCF Service’s operations to a non-SOAP endpoint, and call them using HTTP Methods.

Why use the WCF Web HTTP Programming Model? WCF is a well-established, reliable, secure, enterprise technology. Many large, as well as small, organizations use WCF to build service-oriented applications. However, as communications become increasingly Internet-enabled and mobile, the WCF Web HTTP Programming Model allows us to add the use of simple HTTP methods, such as POST, GET, DELETE, and PUT, to existing WCF services. Adding a web endpoint to an existing WCF service extends its reach to modern end-user platforms with minimal effort. Lastly, using the WCF Web HTTP Programming Model allows us to move toward the increasingly popular RESTful Web Service Model, so many organizations are finally starting to embrace in the enterprise.

Creating the WCF Service

The major steps involved in this example are as follows:

  1. Create a new WCF Service Application Project;
  2. Add the Entity Framework package via NuGet;
  3. Add a Reference the previous Post’s DAL project;
  4. Add a Connection String to the project’s configuration;
  5. Create the WCF Service Contract;
  6. Create the operations the service will expose via a web endpoint;
  7. Configure the service’s behaviors, binding, and web endpoint;
  8. Publish the WCF Service to IIS using VS2012’s Web Project Publishing Tool;
  9. Test the service’s operations with Fiddler.

The WCF Service Application Project

Solution Explorer View of New Solution

Solution Explorer View of New Solution for Reference

Start by creating a new Visual Studio 2012 WCF Service Application Project, named ‘HealthTracker.WcfService’. Add it to a new Solution, named ‘HealthTracker’. The WCF Service Application Project type is specifically designed to be hosted by Microsoft’s Internet Information Services (IIS).

Create New WCF Service Application Project

Create New WCF Service Application Project

Once the Project and Solution are created, install Entity Framework (‘System.Data.Entity’) into the Solution by right-clicking on the Solution and selecting ‘Manage NuGet Packages for Solution…’ Install the ‘EntityFramework’ package. If you haven’t discovered the power of NuGet for Visual Studio, check out their site.

Manage NuGet Packages - Add Entity Framework to Solution

Manage NuGet Packages – Add Entity Framework to Solution

Next, add a Reference in the new Project, to the previous ‘HealthTracker.DataAccess.DbFirst’ Project. When the WCF Service Application Project is built, a copy of the ‘HealthTracker.DataAccess.DbFirst.dll’ assembly will be placed into the ‘bin’ folder of the ‘HealthTracker.WcfService’ Project.

Adding Reference to Previous EF5 Database First Project

Add a Reference to Previous EF5 Database First Project

Next, copy the connection string from the previous project’s ‘App.Config file’ and paste into the new WCF Service Application Project’s ‘Web.config’ file. The connection is required by the ‘HealthTracker.DataAccess.DbFirst.dll’ assembly. The connection string should look similar to the below code.

<connectionStrings>
    <add name="HealthTrackerEntities" connectionString="metadata=res://*/HealthTracker.csdl|res://*/HealthTracker.ssdl|res://*/HealthTracker.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=[Your_Server]\[Your_SQL_Instance];initial catalog=HealthTracker;persist security info=True;user id=DemoLogin;password=[Your_Password];MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
</connectionStrings>

The WCF Service

Delete the default ‘Service.svc’ and ‘IService.cs’ created by the Project Template. You can also delete the default ‘App_Data’ folder. Add a new WCF Service, named ‘HealthTrackerWcfService.svc’. Adding a new service creates both the WCF Service file (.svc), as well as a WCF Service Contract file (.cs), an Interface, named ‘IHealthTrackerWcfService.cs’. The ‘HealthTrackerWcfService’ class implements the ‘IHealthTrackerWcfService’ Interface class (‘public class HealthTrackerWcfService : IHealthTrackerWcfService’).

Add New WCF Service to Project

Add New WCF Service to Project

The WCF Service file contains public methods, called service operations, which the service will expose through a web endpoint. The second file, an Interface class, is referred to as the Service Contract. The Service Contract contains the method signatures of all the operations the service’s web endpoint expose. The Service Contract contains attributes, part of the ‘System.ServiceModel’ and ‘System.ServiceModel.Web’ Namespaces, describing how the service and its operation will be exposed. To create the Service Contract, replace the default code in the file, ‘IHealthTrackerWcfService.cs’, with the following code.

using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Web;
using HealthTracker.DataAccess.DbFirst;

namespace HealthTracker.WcfService
{
    [ServiceContract]
    public interface IHealthTrackerWcfService
    {
        [OperationContract]
        [WebInvoke(UriTemplate = "GetPersonId?name={personName}",
            Method = "GET")]
        int GetPersonId(string personName);

        [OperationContract]
        [WebInvoke(UriTemplate = "GetPeople",
            Method = "GET")]
        List<Person> GetPeople();

        [OperationContract]
        [WebInvoke(UriTemplate = "GetPersonSummaryStoredProc?id={personId}",
            Method = "GET")]
        List<GetPersonSummary_Result> GetPersonSummaryStoredProc(int personId);

        [OperationContract]
        [WebInvoke(UriTemplate = "InsertPerson",
            Method = "POST")]
        bool InsertPerson(Person person);

        [OperationContract]
        [WebInvoke(UriTemplate = "UpdatePerson",
            Method = "PUT")]
        bool UpdatePerson(Person person);

        [OperationContract]
        [WebInvoke(UriTemplate = "DeletePerson?id={personId}",
            Method = "DELETE")]
        bool DeletePerson(int personId);
        
        [OperationContract]
        [WebInvoke(UriTemplate = "UpdateOrInsertHydration?id={personId}",
            Method = "POST")]
        bool UpdateOrInsertHydration(int personId);

        [OperationContract]
        [WebInvoke(UriTemplate = "InsertActivity",
            Method = "POST")]
        bool InsertActivity(Activity activity);

        [OperationContract]
        [WebInvoke(UriTemplate = "DeleteActivity?id={activityId}",
            Method = "DELETE")]
        bool DeleteActivity(int activityId);

        [OperationContract]
        [WebInvoke(UriTemplate = "GetActivities?id={personId}",
            Method = "GET")]
        List<ActivityDetail> GetActivities(int personId);

        [OperationContract]
        [WebInvoke(UriTemplate = "InsertMeal",
            Method = "POST")]
        bool InsertMeal(Meal meal);

        [OperationContract]
        [WebInvoke(UriTemplate = "DeleteMeal?id={mealId}",
            Method = "DELETE")]
        bool DeleteMeal(int mealId);

        [OperationContract]
        [WebInvoke(UriTemplate = "GetMeals?id={personId}",
            Method = "GET")]
        List<MealDetail> GetMeals(int personId);

        [OperationContract]
        [WebInvoke(UriTemplate = "GetPersonSummaryView?id={personId}",
            Method = "GET")]
        List<PersonSummaryView> GetPersonSummaryView(int personId);
    }
}

The service’s operations use a variety of HTTP Methods, including GET, POST, PUT, and DELETE. The operations take a mix of primitive data types, as well as complex objects as arguments. The operations also return the same variety of simple data types, as well as complex objects. Note the operation ‘InsertActivity’ for example. It takes a complex object, an ‘Activity’, as an argument, and returns a Boolean. All the CRUD operations dealing with inserting, updating, or deleting data return a Boolean, indicating success or failure of the operation’s execution. This makes unit testing and error handling on the client-side easier.

Next, we will create the WCF Service. Replace the existing contents of the ‘HealthTrackerWcfService.svc’ file with the following code.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using HealthTracker.DataAccess.DbFirst;

namespace HealthTracker.WcfService
{
    [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
    public class HealthTrackerWcfService : IHealthTrackerWcfService
    {
        private readonly DateTime _today = DateTime.Now.Date;

        #region Service Operations
        /// <summary>
        /// Example of Adding a new Person.
        /// </summary>
        /// <param name="person">New Person Object</param>
        /// <returns>True if successful</returns>
        public bool InsertPerson(Person person)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    dbContext.People.Add(new DataAccess.DbFirst.Person { Name = person.Name });
                    dbContext.SaveChanges();
                    return true;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return false;
            }
        }

        /// <summary>
        /// Example of Updating a Person.
        /// </summary>
        /// <param name="person">New Person Object</param>
        /// <returns>True if successful</returns>
        public bool UpdatePerson(Person person)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    var personToUpdate = dbContext.People.First(p => p.PersonId == person.PersonId);
                    if (personToUpdate == null) return false;
                    personToUpdate.Name = person.Name;
                    dbContext.SaveChanges();
                    return true;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return false;
            }
        }

        /// <summary>
        /// Example of deleting a Person.
        /// </summary>
        /// <param name="personId">PersonId</param>
        /// <returns>True if successful</returns>
        public bool DeletePerson(int personId)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    var personToDelete = dbContext.People.First(p => p.PersonId == personId);
                    if (personToDelete == null) return false;
                    dbContext.People.Remove(personToDelete);
                    dbContext.SaveChanges();
                    return true;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return false;
            }
        }

        /// <summary>
        /// Example of finding a Person's Id.
        /// </summary>
        /// <param name="personName">Name of the Person to find</param>
        /// <returns>Person's unique Id (PersonId)</returns>
        public int GetPersonId(string personName)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    var personId = dbContext.People
                                            .Where(person => person.Name == personName)
                                            .Select(person => person.PersonId)
                                            .First();
                    return personId;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return -1;
            }
        }

        /// <summary>
        /// Returns a list of all People.
        /// </summary>
        /// <returns>List of People</returns>
        public List<Person> GetPeople()
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    var people = (dbContext.People.Select(p => p));
                    var peopleList = people.Select(p => new Person
                                                            {
                                                                PersonId = p.PersonId,
                                                                Name = p.Name
                                                            }).ToList();

                    return peopleList;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return null;
            }
        }
        /// <summary>
        /// Example of adding a Meal.
        /// </summary>
        /// <param name="meal">New Meal Object</param>
        /// <returns>True if successful</returns>
        public bool InsertMeal(Meal meal)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    dbContext.Meals.Add(new DataAccess.DbFirst.Meal
                                            {
                                                PersonId = meal.PersonId,
                                                Date = _today,
                                                MealTypeId = meal.MealTypeId,
                                                Description = meal.Description
                                            });
                    dbContext.SaveChanges();
                    return true;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return false;
            }
        }

        /// <summary>
        /// Example of deleting a Meal.
        /// </summary>
        /// <param name="mealId">MealId</param>
        /// <returns>True if successful</returns>
        public bool DeleteMeal(int mealId)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    var mealToDelete = dbContext.Meals.First(m => m.MealTypeId == mealId);
                    if (mealToDelete == null) return false;
                    dbContext.Meals.Remove(mealToDelete);
                    dbContext.SaveChanges();
                    return true;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return false;
            }
        }

        /// <summary>
        /// Return all Meals for a Person.
        /// </summary>
        /// <param name="personId">PersonId</param>
        /// <returns></returns>
        public List<MealDetail> GetMeals(int personId)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    var meals = dbContext.Meals.Where(m => m.PersonId == personId)
                                         .Select(m => new MealDetail
                                                          {
                                                              MealId = m.MealId,
                                                              Date = m.Date,
                                                              Type = m.MealType.Description,
                                                              Description = m.Description
                                                          }).ToList();
                    return meals;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return null;
            }
        }

        /// <summary>
        /// Example of adding an Activity.
        /// </summary>
        /// <param name="activity">New Activity Object</param>
        /// <returns>True if successful</returns>
        public bool InsertActivity(Activity activity)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    dbContext.Activities.Add(new DataAccess.DbFirst.Activity
                                                 {
                                                     PersonId = activity.PersonId,
                                                     Date = _today,
                                                     ActivityTypeId = activity.ActivityTypeId,
                                                     Notes = activity.Notes
                                                 });
                    dbContext.SaveChanges();
                    return true;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return false;
            }

        }

        /// <summary>
        /// Example of deleting a Activity.
        /// </summary>
        /// <param name="activityId">ActivityId</param>
        /// <returns>True if successful</returns>
        public bool DeleteActivity(int activityId)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    var activityToDelete = dbContext.Activities.First(a => a.ActivityId == activityId);
                    if (activityToDelete == null) return false;
                    dbContext.Activities.Remove(activityToDelete);
                    dbContext.SaveChanges();
                    return true;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return false;
            }
        }

        /// <summary>
        /// Return all Activities for a Person.
        /// </summary>
        /// <param name="personId">PersonId</param>
        /// <returns>List of Activities</returns>
        public List<ActivityDetail> GetActivities(int personId)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    var activities = dbContext.Activities.Where(a => a.PersonId == personId)
                                              .Select(a => new ActivityDetail
                                                               {
                                                                   ActivityId = a.ActivityId,
                                                                   Date = a.Date,
                                                                   Type = a.ActivityType.Description,
                                                                   Notes = a.Notes
                                                               }).ToList();
                    return activities;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return null;
            }
        }

        /// <summary>
        /// Example of updating existing Hydration count.
        /// Else adding new Hydration if it doesn't exist.
        /// </summary>
        /// <param name="personId">PersonId</param>
        /// <returns>True if successful</returns>
        public bool UpdateOrInsertHydration(int personId)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    var existingHydration = dbContext.Hydrations.First(
                        hydration => hydration.PersonId == personId
                                     && hydration.Date == _today);

                    if (existingHydration != null && existingHydration.HydrationId > 0)
                    {
                        existingHydration.Count++;
                        dbContext.SaveChanges();
                        return true;
                    }

                    dbContext.Hydrations.Add(new Hydration
                                                 {
                                                     PersonId = personId,
                                                     Date = _today,
                                                     Count = 1
                                                 });
                    dbContext.SaveChanges();
                    return true;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return false;
            }
        }

        /// <summary>
        /// Return a count of all Meals, Hydrations, and Activities for a Person.
        /// Based on a Database View (virtual table).
        /// </summary>
        /// <param name="personId">PersonId</param>
        /// <returns>Summary for a Person</returns>
        public List<PersonSummaryView> GetPersonSummaryView(int personId)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    var personView = (dbContext.PersonSummaryViews
                                               .Where(p => p.PersonId == personId))
                                               .ToList();
                    return personView;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return null;
            }
        }

        /// <summary>
        /// Return a count of all Meals, Hydrations, and Activities for a Person.
        /// Based on a Stored Procedure.
        /// </summary>
        /// <param name="personId">PersonId</param>
        /// <returns>Summary for a Person</returns>
        public List<GetPersonSummary_Result> GetPersonSummaryStoredProc(int personId)
        {
            try
            {
                using (var dbContext = new HealthTrackerEntities())
                {
                    var personView = (dbContext.GetPersonSummary(personId)
                                               .Where(p => p.PersonId == personId))
                                               .ToList();
                    return personView;
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                return null;
            }
        }

        #endregion
    }

    #region POCO Classes

    public class Person
    {
        public int PersonId { get; set; }
        public string Name { get; set; }
    }

    public class Meal
    {
        public int PersonId { get; set; }
        public int MealTypeId { get; set; }
        public string Description { get; set; }
    }

    public class MealDetail
    {
        public int MealId { get; set; }
        public DateTime Date { get; set; }
        public string Type { get; set; }
        public string Description { get; set; }
    }

    public class Activity
    {
        public int PersonId { get; set; }
        public int ActivityTypeId { get; set; }
        public string Notes { get; set; }
    }

    public class ActivityDetail
    {
        public int ActivityId { get; set; }
        public DateTime Date { get; set; }
        public string Type { get; set; }
        public string Notes { get; set; }
    }

    #endregion
}

Each method instantiates an instance of ‘HeatlthTrackerEntities’, Referenced by the project and accessible to the class via the ‘using HealthTracker.DataAccess.DbFirst;’ statement, ‘HeatlthTrackerEntities’ implements ‘System.Data.Entity.DBContext’. Each method uses LINQ to Entities to interact with the Entity Data Model, through the ‘HeatlthTrackerEntities’ object.

In addition to the methods (service operations) contained in the HealthTrackerWcfService class, there are several POCO classes. Some of these POCO classes, such as ‘NewMeal’ and ‘NewActivity’, are instantiated to hold data passed in the operation’s arguments by the client Request message. Other POCO classes, such as ‘MealDetail’ and ‘ActivityDetail’, are instantiated to hold data passed back to the client by the operations, in the Response message. These POCO instances are serialized to and deserialized from JSON or XML.

The WCF Service’s Configuration

The most complex and potentially the most confusing part of creating a WCF Service, at least for me, is always the service’s configuration. Due in part to the flexibility of WCF Services to accommodate many types of client, server, network, and security situations, the configuration of the services takes an in-depth understanding of bindings, behaviors, endpoints, security, and associated settings. The best books I’ve found on configuring WCF Services is Pro WCF 4: Practical Microsoft SOA Implementation, by Nishith Pathak. The book goes into great detail on all aspects of configuring WCF Services to meet your particular project’s needs.

Since we are only using the WCF Web HTTP Programming Model to build and expose our service, the ‘webHttpBinding’ binding is the only binding we need to configure. I have made an effort to strip out all the unnecessary boilerplate settings from our service’s configuration.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    </configSections>
    <appSettings>
        <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
    </appSettings>
    <system.web>
        <compilation debug="true" targetFramework="4.5" />
        <httpRuntime targetFramework="4.5" />
    </system.web>
    <system.serviceModel>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
        <behaviors>
            <endpointBehaviors>
                <behavior name="webHttpBehavior">
                    <webHttp helpEnabled="true" defaultOutgoingResponseFormat="Json"
                             defaultBodyStyle="Bare" automaticFormatSelectionEnabled="true"/>
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <services>
            <service name="HealthTracker.WcfService.HealthTrackerWcfService">
                <endpoint address="web" binding="webHttpBinding" behaviorConfiguration="webHttpBehavior"
                          contract="HealthTracker.WcfService.IHealthTrackerWcfService" />
            </service>
        </services>
    </system.serviceModel>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
        <directoryBrowse enabled="false" />
    </system.webServer>
    <entityFramework>
        <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
    </entityFramework>
    <connectionStrings>
        <add name="HealthTrackerEntities" connectionString="metadata=res://*/HealthTracker.csdl|res://*/HealthTracker.ssdl|res://*/HealthTracker.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=gstafford-windows-laptop\DEVELOPMENT;initial catalog=HealthTracker;persist security info=True;user id=DemoLogin;password=DemoLogin123;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
    </connectionStrings>
</configuration>

Some items to note in the configuration:

  • Line 4: Entity Framework – The Entity Framework 5 reference you added earlier via NuGet.
  • Line 18: Help – This enables an automatically generated Help page, displaying all the service’s operations for the endpoint, with details on how to call each operation.
  • Lines 18-19: Request and Response Message Formats – Default settings for message format and body style of Request and Response messages. In this case, JSON and Bare. Setting defaults saves lots of time, not having to add attributes to each individual operation.
  • Line 25-26: Endpoint – The service’s single endpoint, with a single binding and behavior. For this Post, we are only using the ‘webHttpBinding’ binding type.
  • Line 38: Connection String – The SQL Server Connection String you copied from the previous Post’s Project. Required by the DAL Project Reference you added, earlier.

Deploying the Service to IIS

Now that the service is complete, we will deploy and host it in IIS. There are many options when it comes to creating and configuring a new website – setting up domain names, choosing ports, configuring firewalls, defining bindings, setting permissions, and so forth. This Post will not go into that level of detail. I will demonstrate how I chose to set up my website and publish my WCF Service.

We need a physical location to deploy the WCF Service’s contents. I recommend a location outside of the IIS root directory, such as ‘C:\HealthTrackerWfcService’. Create this folder on the server where you will be running IIS, either locally or remotely. This folder is where we will publish the service’s contents to from Visual Studio, next.

Create a new website in IIS to host the service. Name the site ‘HealthTracker’. You can configure and use a domain name or a specific port, from which to call the service. I chose to configure a domain name on my IIS Server, ”WcfService.HealthTracker.com’. If you are unsure how to setup a new domain name on your network, then a local, open port is probably easier for you. Pick any random port, like 15678.

Create New Website in IIS to Host Service

Create New Website in IIS to Host Service

Publish the WCF Service to the deployment location, explained above, using Visual Studio 2012’s Web Project Publishing Tool. Exactly how and where you set-up your website, and any security considerations, will affect the configuration of the Publishing Tool’s Profile. My options will not necessarily work for your specific environment.

Testing the WCF Service

Congratulations, your service is deployed. Now, let’s see if it works. Before we test the individual operations, we will ensure the service is being hosted correctly. Open the service’s Help page. This page automatically shows details on all operations of a particular endpoint. The address should follow the convention of http://%5Byour_domain%5D:%5Byour_port%5D/%5Byour_service%5D/%5Byour_endpoint_address%5D/help. In my case ‘http://wcfservice.healthtracker.com/HealthTrackerWcfService.svc/web/help&#8217;. If this page displays, then the service is deployed correctly and it’s web endpoint is responding as expected.

WCF Service Operations Displayed at Help URL

WCF Service Help Page – Service Endpoint Operations

While on the Help page, click on any of the HTTP Methods to see a further explanation of that particular operation. This page is especially useful for copying the URL of the operation for use in Fiddler. It is even more useful for grabbing the sample JSON or XML Request messages. Just substitute your test values for the default values, in Fiddler. It saves a lot of typing and many potential errors.

WCF Service Help Page - Example Request Body

WCF Service Help Page – Example Request Body

Fiddler

The easiest way to test each of the service’s operations is Fiddler. Download and install Fiddler, if you don’t already have it. Using Fiddler, construct a Request message and call the operations by executing the operation’s associated HTTP Method. Below is an example of calling the ‘InsertActivity’ operation. This CRUD operation accepts a new Activity object as an argument, inserts into the database via the Entity Data Model, and returns a Boolean value indicating success.

To call the ‘InsertActivity’ operation, 1) select the ‘POST’ HTTP method, 2) input the URL for the ‘InsertActivity’ operation, 3) select a version of HTTP (1.2), 4) input the Content-Type (JSON or XML) in the Request Headers section, 5) input the body of the Request, a new ‘Activity’ as JSON, in the Request Body section, and 6) select ‘Execute’. The 7) Response should appear in the Web Sessions window.

Fiddler Example - InsertActivity Operation Request

Fiddler Example – InsertActivity Operation Request

Executing the 1) Request (constructed above), should result in a 2) Response in the Web Sessions window. Double clicking on the Web Session should result in the display of the 3) Response message in the lower righthand window. The operation returns a Boolean indicating if the operation succeeded or failed. In this case, we received a value of ‘true’.

Fiddler Example - InsertActivity Operation Response

Fiddler Example – InsertActivity Operation Response

To view the Activity we just inserted, we need to call the ‘GetActivities’ operation, passing it the same ‘PersonId’ argument. In Fiddler, 1) select the ‘GET’ HTTP method, 2) input the URL for the ‘GetActivities’ operation including a value for the ‘PersonId’ argument, 3) select the desired version of HTTP (1.2), 4) input a Content-Type (JSON or XML) in the Request Headers section, and 5) select ‘Execute’. Same as before, the 6) Response should appear in the Web Sessions window. This time there is no Request body content.

Fiddler Example - GetActivities Operation Request

Fiddler Example – GetActivities Operation Request

As before, executing the 1) Request should result in a 2) Response in the Web Sessions window. Doubling clicking on the Web Session should result in the display of the 3) Response in the lower left window. This method returns a JSON payload with each Activity, associated with the PersonId argument.

Fiddler Example - GetActivities Operation Response

Fiddler Example – GetActivities Operation Response

You can use this same process to test all the other operations at the WCF Service’s endpoint. You can also save the Request message or complete Web Sessions in Fiddler should you need to re-test.

Conclusion

We now have a WCF Service deployed and running in IIS, and tested. The service’s operations can be called from any application capable of making an HTTP call. Thank you for taking the time to read this Post. I hope you found it beneficial.

, , , , , , , , , , , , , , , , ,

1 Comment

First Impressions of Code First Development with Entity Framework 5 in Visual Studio 2012

Build a Data Access Layer (DAL) in Visual Studio 2012 using the recently-released Entity Framework 5 with Code First Development. Leverage Entity Framework 5’s eagerly anticipated enum support, as well as Code First Migrations and Lazy Loading functionality. An updated version of this project’s source code, using EF6 is now available on GitHub. The GitHub repository contains all three Entity Framework blog posts.

Introduction

In August of this year (2012), along with the release of Visual Studio 2012, Microsoft announced the release of Entity Framework 5 (EF5). According to Microsoft, “Entity Framework (EF) is an object-relational mapper that enables .NET developers to work with relational data using domain-specific objects. It eliminates the need for most of the data-access code that developers usually need to write.

EF5 offers multiple options for development, including Model First, Database First, and Code First. Code First, first introduced in EF4.1, allows you to define your model using C# or VB.Net POCO classes. Additional configuration can optionally be performed using attributes on your classes and properties or by using a fluent API. Code First will then create a database if it doesn’t exist, or Code First will use an existing empty database, adding new tables, according to Microsoft.

Exploring Code First

I started by looking at EF5’s Code First development to create a new database. I chose to create a Data Access Layer (DAL) for a health tracker application. This was loosely based on a mobile application I developed some time ago. You can track food intake, physical activities, and how much water you drink per day.

To further explore EF5’s new enum support feature, I substituted two of the database tables, containing static ‘system’ data, for enumeration classes. Added in EF5, Enumeration (enum) support, was the number one user-requested EF feature. Also added to EF5 were spatial data types and my personal favorite, table-valued functions (TVF).

Once the new database was created, I tested another newer EF feature, Code First Migrations. Code First Migrations, introduced in EF4.3, is a feature that allows a database created by Code First to be incrementally changed as your Code First model evolves.

Creating the HealthTracker DAL

Final View of Both Projects in Solution

Final View of Both Projects in Solution

Here are the steps I followed to create my EF5 Entity Framework DAL project:

1. Create a new C# Class Library, ‘HealthTracker.DataAccess’, in a Solution, ‘HealthTracker’. Target the .NET Framework 4.5.

New Class Library

New Class Library

2. Create (4) POCO (Plain Old CLR Object) classes: Person, Activity, Meal, and Hydration, shown below. Note the virtual properties. According to Microsoft, this enables the Lazy Loading feature of Entity Framework. Lazy Loading means that the contents of these properties will be automatically loaded from the database when you try to access them.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace HealthTracker.DataAccess.Classes
{
    public class Person
    {
        public int PersonId { get; set; }
        public string Name { get; set; }
        public virtual List<Meal> Meals { get; set; }
        public virtual List<Activity> Activities { get; set; }
        public virtual List<Hydration> Hydrations { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace HealthTracker.DataAccess.Classes
{
    public class Activity
    {
        public int ActivityId { get; set; }
        public DateTime Date { get; set; }
        public ActivityType Type { get; set; }
        public string Notes { get; set; }
        public int PersonId { get; set; }
        public virtual Person Person { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace HealthTracker.DataAccess.Classes
{
    public class Hydration
    {
        public int HydrationId { get; set; }
        public DateTime Date { get; set; }
        public int Count { get; set; }
        public int PersonId { get; set; }
        public virtual Person Person { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace HealthTracker.DataAccess.Classes
{
    public class Meal
    {
        public int MealId { get; set; }
        public DateTime Date { get; set; }
        public MealType Type { get; set; }
        public string Description { get; set; }
        public int PersonId { get; set; }
        public virtual Person Person { get; set; }
    }
}

3. Add data annotations to the POCO classes. Annotations are used to specify validation for fields in the data model. Data annotations can include required, min, max, string length, and so forth. Annotations are part of the System.ComponentModel.DataAnnotations namespace. As an alternative to annotations, you can use the Code First Fluent API.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace HealthTracker.DataAccess.Classes
{
    public class Person
    {
        public int PersonId { get; set; }

        [Required]
        [StringLength(100,
        ErrorMessage = "Name must be less than 100 characters."),
        MinLength(2,
        ErrorMessage = "Name must be less than 2 characters.")]
        public string Name { get; set; }

        public virtual List<Meal> Meals { get; set; }
        public virtual List<Activity> Activities { get; set; }
        public virtual List<Hydration> Hydrations { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace HealthTracker.DataAccess.Classes
{
    public class Activity
    {
        public int ActivityId { get; set; }

        [Required]
        public DateTime Date { get; set; }

        [Required]
        [EnumDataType(typeof(ActivityType))]
        public ActivityType Type { get; set; }

        [StringLength(100,
        ErrorMessage = "Note must be less than 100 characters.")]
        public string Notes { get; set; }

        [Required]
        public int PersonId { get; set; }
        public virtual Person Person { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace HealthTracker.DataAccess.Classes
{
    public class Hydration
    {
        public int HydrationId { get; set; }

        [Required]
        public DateTime Date { get; set; }

        [Required]
        [Range(0, 20,
        ErrorMessage = "Hydration amount must be between 0 - 20.")]
        public int Count { get; set; }

        [Required]
        public int PersonId { get; set; }
        public virtual Person Person { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace HealthTracker.DataAccess.Classes
{
    public class Meal
    {
        public int MealId { get; set; }

        [Required]
        public DateTime Date { get; set; }

        [Required]
        [EnumDataType(typeof(MealType))]
        public MealType Type { get; set; }

        [StringLength(100,
        ErrorMessage = "Description must be less than 100 characters.")]
        public string Description { get; set; }

        [Required]
        public int PersonId { get; set; }
        public virtual Person Person { get; set; }
    }
}

4. Create (2) enum classes: MealType and ActivityType, shown below.

using System;
using System.Collections.Generic;
using System.Linq;

namespace HealthTracker.DataAccess.Classes
{
    public enum ActivityType
    {
        Treadmill,
        Jogging,
        WeightTraining,
        Biking,
        Aerobics,
        Other
    }
}
using System;
using System.Collections.Generic;
using System.Linq;

namespace HealthTracker.DataAccess.Classes
{
    public enum MealType
    {
        Breakfast,
        MidMorning,
        Lunch,
        MidAfternoon,
        Dinner,
        Snack,
        Brunch,
        Other
    }
}

5. Add Entity Framework (System.Data.Entity namespace classes) to the Solution by right-clicking on the Solution and selecting ‘Manage NuGet Packages for Solution…’ Install the ‘EntityFramework’ package. If you haven’t discovered the power of NuGet with Visual Studio, check out their site.

Manage NuGet Packages - Add Entity Framework

Manage NuGet Packages – Add Entity Framework

6. Add a DbContext class, ‘HealthTrackerContext’, shown below. According to Microsoft, “DbContext represents a combination of the Unit-Of-Work and Repository patterns and enables you to query a database and group together changes that will then be written back to the store as a unit.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;
using HealthTracker.DataAccess.Classes;

namespace HealthTracker.DataAccess
{
    public class HealthTrackerContext : DbContext
    {
        public DbSet<Activity> Activities { get; set; }
        public DbSet<Hydration> Hydrations { get; set; }
        public DbSet<Meal> Meals { get; set; }
        public DbSet<Person> Persons { get; set; }
    }
}

7. Add ‘Code First Migrations’ by running the Enable-Migrations command in the NuGet Package Manager Console. This allowed me to make changes to POCO classes, while keeping the database schema in sync. This only needs to be run once.

Enable-Migrations -ProjectName HealthTracker.DataAccess -Force -EnableAutomaticMigrations -Verbose
Package Manager Console - Code First Migrations

Package Manager Console – Code First Migrations

This creates the following class in a new Migrations folder, called Configuration.cs

namespace HealthTracker.DataAccess.Migrations
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;

    internal sealed class Configuration : DbMigrationsConfiguration<HealthTracker.DataAccess.HealthTrackerContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
        }

        protected override void Seed(HealthTracker.DataAccess.HealthTrackerContext context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method
            //  to avoid creating duplicate seed data. E.g.
            //
            //    context.People.AddOrUpdate(
            //      p => p.FullName,
            //      new Person { FullName = "Andrew Peters" },
            //      new Person { FullName = "Brice Lambson" },
            //      new Person { FullName = "Rowan Miller" }
            //    );
            //
        }
    }
}

Running the Update-Database command in Package Manager Console updates the database when you have made changes.

Update-Database -ProjectName HealthTracker.DataAccess -Verbose -Force

I found the Code First Migrations feature a bit confusing at first. It took several tries to work out the correct command to use to add Code First Migrations to my data access project, and the command to call later, to update the database based on my project.

Dependency Graph

Here is the way the class relationships should look using Visual Studio 2012’s powerful new Dependency Graph feature:

Visual Studio 2012 Dependency Graph of HealthTracker

Visual Studio 2012 Dependency Graph of HealthTracker

Testing the Project

To test the entities, I created a simple console application, which references the above EF5 class library. The application takes a person’s name as input. It then adds and/or updates Activities, Meals, and Hydrations, for that Person, depending on whether or not that Person already exists in the database.

1. Create a new C# Console Application, ‘HealthTracker.Console’.

New Console Application

New Console Application

2. Add a reference to the ‘HealthTracker.DataAccess’ project.

Add Reference to HealthTracker.DataAccess Project

Add Reference to HealthTracker.DataAccess Project

3. Create the Examples class and add the code below to the Main method class. As seen in the Examples class, using Micorsoft’s LINQ to Entities with the new .NET Framework 4.5, makes querying the entities very easy.

using System;

namespace HealthTracker.ConsoleApp
{
    class Program
    {
        private static string _newName = String.Empty;

        static void Main()
        {
            GetNameInput();
            var personId = Examples.FindPerson(_newName);

            if (personId == 0)
            {
                Examples.CreatePerson(_newName);
                personId = Examples.FindPerson(_newName);
            }

            Examples.CreateActivity(personId);
            Examples.CreateMeals(personId);
            Examples.UpdateOrAddHydration(personId);

            Console.WriteLine("Click any key to quit.");
            Console.ReadKey();
        }

        private static void GetNameInput()
        {
            Console.Write("Input Person's Name: ");
            var readLine = Console.ReadLine();
            if (readLine != null) _newName = readLine.Trim();
            if (_newName.Length > 2) return;
            Console.WriteLine("Name to short. Exiting program.");
            GetNameInput();
        }
    }
}
using System;
using System.Globalization;
using System.Linq;
using HealthTracker.DataAccess;
using HealthTracker.DataAccess.Classes;

namespace HealthTracker.ConsoleApp
{
    public class Examples
    {
        private static readonly DateTime Today = DateTime.Now.Date;

        /// <summary>
        /// Example of finding a Person in database.
        /// </summary>
        /// <param name="name">Name of the Person</param>
        /// <returns>Person's unique PersonId</returns>
        public static int FindPerson(string name)
        {
            using (var db = new HealthTrackerContext())
            {
                var personId = db.Persons.Where(person => person.Name == name)
                    .Select(person => person.PersonId).FirstOrDefault();

                if (personId == 0)
                    Console.WriteLine("Person, {0}, could not be found...", name);
                else
                    Console.WriteLine("PersonId {0} retrieved...", personId);

                return personId;
            }
        }

        /// <summary>
        /// Example of Adding a new Person
        /// </summary>
        /// <param name="name">Name of the Person</param>
        public static void CreatePerson(string name)
        {
            using (var db = new HealthTrackerContext())
            {
                // Add a new Person
                db.Persons.Add(new Person { Name = name });
                db.SaveChanges();
                Console.WriteLine("New Person, {0}, added...", name);
            }
        }

        /// <summary>
        /// Example of updating existing Hydration count.
        /// Else adding new Hydration if it doesn't exist.
        /// </summary>
        /// <param name="personId">Person's unique PersonId</param>
        public static void UpdateOrAddHydration(int personId)
        {
            using (var db = new HealthTrackerContext())
            {
                var existingHydration = db.Hydrations.FirstOrDefault(
                    hydration => hydration.PersonId == personId
                        && hydration.Date == Today);

                if (existingHydration != null && existingHydration.HydrationId > 0)
                {
                    existingHydration.Count++;
                    db.SaveChanges();
                    Console.WriteLine("Existing Hydration count increased to {0}...",
                        existingHydration.Count.ToString(CultureInfo.InvariantCulture));
                    return;
                }

                db.Hydrations.Add(new Hydration
                {
                    PersonId = personId,
                    Date = Today,
                    Count = 1
                });
                db.SaveChanges();
                Console.WriteLine("New Hydration added...");
            }
        }

        /// <summary>
        /// Example of adding two Meals.
        /// </summary>
        /// <param name="personId">Person's unique PersonId</param>
        public static void CreateMeals(int personId)
        {
            using (var db = new HealthTrackerContext())
            {
                db.Meals.Add(new Meal
                {
                    PersonId = personId,
                    Date = Today,
                    Type = MealType.Breakfast,
                    Description = "(2) slices toast, (1) glass orange juice"
                });

                db.Meals.Add(new Meal
                {
                    PersonId = personId,
                    Date = Today,
                    Type = MealType.Lunch,
                    Description = "(1) protein shake, (1) apple"
                });

                db.SaveChanges();
                Console.WriteLine("Two new Meals added...");
            }
        }

        /// <summary>
        /// Example of adding an Activity
        /// </summary>
        /// <param name="personId">Person's unique PersonId</param>
        public static void CreateActivity(int personId)
        {
            using (var db = new HealthTrackerContext())
            {
                db.Activities.Add(new Activity
                {
                    PersonId = personId,
                    Date = Today,
                    Type = ActivityType.Treadmill,
                    Notes = "30 minutes, 500 calories"
                });

                db.SaveChanges();
                Console.WriteLine("New Activity added...");
            }
        }
    }
}

The console output below demonstrates the addition a new Person, Susan Jones, with an associated Activity, two Meals, and a Hydration.

Console Output - Adding New Person

Console Output – Adding New Person

The console output below demonstrates inputting Susan Jones a second time. Observe what happened to the output.

Console Output - Updating Existing Person

Console Output – Updating Existing Person

Below is a view of the newly created SQL Express ‘HealthTracker.DataAccess.HealthTrackerContext’ database. Note the four database tables, which correspond to the four POCO classes in the project. Why did the database show up in SQL Express or LocalDB? Read the ‘Where’s My Data?‘ section of this tutorial by Microsoft. Code First uses SQL Express or LocalDB by default, but you add a connection to your project to target SQL Server.

View of HealthTracker SQL Express Database

View of HealthTracker SQL Express Database

You can switch your connection SQL Server at anytime, even after the SQL Express or LocalDB database has been created, and updated using Code First Migrations. That’s what I chose to do after completing this post’s example. I prefer doing my initial development with a temporary SQL Express or LocalDB database while the entities and database schema are still evolving. Once I have the model a fairly stable point, I create the SQL Server database, and continue from there evolving the schema.

Conclusion

Having worked with earlier editions of Entity Framework’s Database First and Model First development, in addition to other ORM packages, I was impressed with EF5’s new features as well as the Code First development methodology. Overall, I feel EF5 is another big step toward making Entity Framework a top-notch ORM, on par with other established products on the market.

, , , , , , , , , , , , , , , , , , , , , , ,

5 Comments