Case Study: Introducing the Knowledge Base Reference Application

Throughout the rest of this book, I’ll use a knowledge base application to demonstrate service design principles and AJAX architecture patterns. A knowledge base is simply a specialized database application used for knowledge management. In this chapter, I’ll describe a generic knowledge base service application that’s not tied to a specific end-user application. Access to the knowledge base is enabled through a service-oriented WCF application. When discussing WCF, I’ll use the case study as a reference application for building AJAX-friendly services. In later chapters, I’ll demonstrate how to develop AJAX components based on the services built in the first few chapters.

Although the application is simple—because it is built to demonstrate service principles, WCF technologies, and AJAX implementations—this architecture is the same basic architecture we use at NewsGator for major commercial applications. Customers might or might not use the slick AJAX user interface that we’ve provided, but many customers develop applications that integrate our Web services API, and most customers use additional client or server applications connected through the API. One particular value of NewsGator’s product line is the Web services platform which is also at the core of integrated applications that run on Windows and Macintosh desktops, Microsoft Office applications, and Microsoft Office SharePoint Server instances. While the technology platforms vary drastically, each application is integrated through the Web services platform, either with NewsGator Online Web services or instances of NewsGator Enterprise Server running inside the corporate firewall.

The sample knowledge base application uses catalogs of data entry items and stores multiple versions of each data entry so that multiple users can edit items. This allows the data to be used for versioned documents, including wikis, blogs, and general purpose data. Each catalog exposes a feed of recently posted items and a search interface, although users can also search multiple catalogs on the basis of content, title, and tags. (A tag is simply a keyword associated with a data item.) Figure 2-3 shows the basic database schema for the knowledge base application. This schema also serves as the basic data model exposed through WCF contracts in the sample application.

The knowledge base reference application database schema.

Figure 2-3. The knowledge base reference application database schema.

To implement the knowledge base application, I defined a single data access class that implements data access code to persist the knowledge base data to SQL Server. In a production application, code for this purpose would typically be implemented in an assembly separate from the service interfaces to further isolate the service endpoints from the implementation details. For our purposes, the data access class is as simple as possible and removes the implementation details from our code samples so that we can focus on the WCF components for our AJAX application. Figure 2-4 displays the class diagram for the DataAccess class. In the following section, I’ll describe the principles of contract-based programming and service-oriented WCF development.

The DataAccess class implements the business logic for the service application.

Figure 2-4. The DataAccess class implements the business logic for the service application.

More Information

More Information

The full code for the DataAccess class is available as part of the chapter’s sample code. Go to http://www.microsoft.com/mspress/companion/9780735625914 to download the code.

Contract-Based Service Programming

WCF services are based on contracts. Contracts are defined for both services and messages, and data contracts can be defined for the message schema. Data contracts are not required for simple types, XML types such as XmlDocument, or for types that use the XmlSerializer class. A contract can also be expressed through a universal contract by using the Message type, which describes the entire message.

In the first service contract example, I defined a catalog service that enables the creation of catalogs and lists available catalogs. In the data model, the catalog is the root-level item that data entries belong to. It is an abstract concept that could be exposed as a blog, wiki, or general purpose list. To keep it simple, I defined the catalog with a simple string definition. Example 2-5 defines the ServiceContract for the catalog service by defining the ICatalogService interface. Because the service returns a simple string result and not a complex type, I did not need to define a data contract for this service.

Example 2-5. The CatalogService message contract uses simple strings to identify catalogs (KnowledgeBase/ICatalogService.cs).

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace KnowledgeBase
{
    [ServiceContract(Name = "CatalogService",
        Namespace = "http://knowledgebase/CatalogService")]
    public interface ICatalogService
    {
        [OperationContract]
        void Create(string catalog);

        [OperationContract]
        List<string> GetCatalogs();
    }
}

To implement the service, I created a class that implements the ICatalogService interface, using object-oriented classes in the business logic to handle the implementation. Because we’re using the security principal HttpContext.Current.User, I marked the class with the AspNetCompatibilityRequirements attribute and set the compatibility level to Required by using AspNetCompatibilityRequirementsMode.Required. Example 2-6 contains the implementation of the concrete CatalogService class.

Example 2-6. The CatalogService implementation is lightweight and handles the service logic while deferring business logic to the implementation (KnowledgeBase/CatalogService.cs).

using System;
using System.Collections.Generic;
using System.Web;
using KnowledgeBase.Implementation;
using System.ServiceModel.Activation;

namespace KnowledgeBase
{
    [AspNetCompatibilityRequirements(
        RequirementsMode=AspNetCompatibilityRequirementsMode.Required)]
    public class CatalogService : ICatalogService
    {
        public void Create(string catalog)
        {
            var dataImplemenation =
                new DataAccess(HttpContext.Current.User);
            dataImplemenation.Create(catalog);
        }

        public List<string> GetCatalogs()
        {
            var dataImplemenation = new
                DataAccess(HttpContext.Current.User);
            return (dataImplemenation.GetCatalogs());
        }
    }
}

Implementing Data Schema Through WCF Data Contracts

As I mentioned, data contracts are not needed for simple types such as the string example I used for catalogs, nor are they required for types that serialize to XML, such as XmlDocument. For complex types, however, you should write a data contract. These contracts form the message contracts when they are joined with the service contracts. The data contract formalizes the data structure and defines the data schema of the message. Applied to AJAX development, you can program the JavaScript client against the data type defined by DataContract, or you can build XSLT style sheets that render the schema. To define the data contract in WCF, you use attributes in the System.Runtime.Serialization namespace, specifically DataContractAttribute applied to the class and DataMemberAttribute applied to each field that will be serialized to the message stream. Example 2-7 lists the data contract for the DataItem type. The data contract defines the XML name Item and the namespace http://knowledgeBase.

Example 2-7. The KnowledgeBase DataItem data contract defines a complex XML type (KnowledgeBase/DataItem.cs).

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace KnowledgeBase
{
    /// <summary>Represents an item in a knowledgebase.</summary>
    [DataContract(Name = "Item", Namespace = "http://knowledgebase")]
    public class DataItem
    {
        [DataMember(Name="Title", Order=1)]
        public string Title { get; set; }

        [DataMember(Name="Body", Order=2)]
        public string Body { get; set; }

        private string format = "wiki";
        [DataMember(Name = "Format", Order = 3)]
        public string Format {
            get { return this.format; } set { this.format = value; } }

        [DataMember(Name = "Version", Order = 4)]
        public int Version { get; set; }

        [DataMember(Name = "LastModifiedBy", Order = 5)]
        public string LastModifiedBy{ get; set; }

        [DataMember(Name = "LastModified", Order = 6)]
        public DateTime LastModified { get; set; }

        [DataMember(Name = "Created", Order = 7)]
        public DateTime Created { get; set; }

        [DataMember(Name = "CreatedBy", Order = 8)]
        public string CreatedBy { get; set; }

        private List<string> tags = new List<string>(0);
        [DataMember(Name = "Tags", Order = 9)]
        public List<string> Tags {
            get { return this.tags; } set { this.tags = value;  } }

        [DataMember(Name = "Catalog", Order = 10)]
        public string Catalog { get; set; }
    }
}

The WCF runtime uses the data contract shown in Example 2-7 to generate the XML schema definition (XSD) shown in Example 2-8. This XSD definition is referenced by WSDL files and allows remote clients to program against the XML schema of the WCF data contract.

Example 2-8. The data contract will generate an XSD in the WSDL files (Auto generated WSDL resource).

<?xml version="1.0" encoding="utf-8"?>
<xs:schema elementFormDefault="qualified" targetNamespace="http://knowledgebase"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://knowledgebase">
  <xs:import
      schemaLocation="http://localhost:8080/Web/DataService.svc?xsd=xsd3"
      namespace="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/>
  <xs:complexType name="Item">
    <xs:sequence>
      <xs:element minOccurs="0" name="Title" nillable="true" type="xs:string"/>
      <xs:element minOccurs="0" name="Body" nillable="true" type="xs:string"/>
      <xs:element minOccurs="0" name="Format" nillable="true" type="xs:string"/>
      <xs:element minOccurs="0" name="Version" type="xs:int"/>
      <xs:element minOccurs="0" name="LastModifiedBy"
          nillable="true" type="xs:string"/>
      <xs:element minOccurs="0" name="LastModified" type="xs:dateTime"/>
      <xs:element minOccurs="0" name="Created" type="xs:dateTime"/>
      <xs:element minOccurs="0" name="CreatedBy" nillable="true" type="xs:string"/>
      <xs:element minOccurs="0" name="Tags" nillable="true" type="q1:ArrayOfstring"
          xmlns:q1="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/>
      <xs:element minOccurs="0" name="Catalog" nillable="true" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
  <xs:element name="Item" nillable="true" type="tns:Item"/>
</xs:schema>

Finally, the data contract in listing 2-9 is serialized into the following XML:

<Item xmlns="http://knowledgebase" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Title>Test</Title>
  <Body>Test Wiki Content</Body>
  <Format>wiki</Format>
  <Version>8</Version>
  <LastModifiedBy>Daniel Larson</LastModifiedBy>
  <LastModified>2008-02-24T23:32:05.407</LastModified>
  <Created>2008-02-24T23:26:01.203</Created>
  <CreatedBy>Daniel Larson</CreatedBy>
  <Tags xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
    <a:string>Test</a:string>
  </Tags>
  <Catalog>Default</Catalog>
</Item>

To render this data contract on the client, you could use XSLT or DOM-based JavaScript code built against this data schema. Any service that uses the http://knowledgebase/Item object could use the same common rendering logic.

Example 2-9 defines the data service interface for the main CRUD (create, read, update, delete) operations. In the data service example, the DataItem is passed into and out of the service, letting any client that understands the item element defined in the http://knowledgeBase XML namespace consume the service.

Example 2-9. The service contract definition for IDataService uses the DataItem data contract (KnowledgeBase/IDataService.cs).

using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.ServiceModel.Syndication;
namespace KnowledgeBase
{
    [ServiceContract(Namespace="http://knowledgebase/",
            Name="DataService")]
    public interface IDataService
    {
        [WebGet]
        [OperationContract(Action="Get")]
        DataItem GetData(string catalog, string title);

        [OperationContract(Action="Save")]
        void SaveData(DataItem data);

        [OperationContract(Action = "Delete")]
        void Delete(DataItem data);
    }
}

To implement the service, you simply create a class that implements the service contract interface. Keep in mind that the service class can implement several interfaces. Later we’ll define additional endpoints that use the same service implementation. Example 2-10 defines the service implementation for the IDataService interface.

Example 2-10. The service implementation for IDataService implements the interface and uses minimal logic to process incoming messages (KnowledgeBase/DataService.cs).

using System;
using System.ServiceModel.Web;
using System.ServiceModel;
using System.ServiceModel.Activation;

using KnowledgeBase.Implementation;
using System.Security.Principal;
using System.Web;

namespace KnowledgeBase
{
    [AspNetCompatibilityRequirements(
        RequirementsMode=AspNetCompatibilityRequirementsMode.Required)]
    public class DataService : IDataService
    {
        public DataItem GetData(string catalog, string title)
        {
            if (string.IsNullOrEmpty(title))
                title = "default";
            if (string.IsNullOrEmpty(catalog))
                catalog = "default";
            var dataImplementation =
                new DataAccess(HttpContext.Current.User);
            return dataImplementation.GetData(catalog, title);
        }
        public void SaveData(DataItem data)
        {
            var dataImplementation =
                new DataAccess(HttpContext.Current.User);
            dataImplementation.SaveData(data);
        }

        public void Delete(DataItem data)
        {
            var dataImplementation =
                new DataAccess(HttpContext.Current.User);
            dataImplementation.Delete(data);
        }
    }
}

In the previous examples, I demonstrated contract-based programming techniques for WCF by using simple types and through complex types that use a data contract. I also kept the service implementation as minimal as possible, leaving the details to object-oriented code that is implemented through a data access class in the sample code. After defining the contract and implementation, I can now expose the endpoints through SVC files and the ASP.NET web.config file to begin using them in the ASP.NET AJAX application and remote clients.

The first step in defining the endpoints is to define some common behavior configurations. You don’t want to have one behavior configuration per endpoint because that gets messy very quickly—instead, define some common configurations that can be used by your services. I’ve found generally that one service behavior configuration and two endpoint behavior configurations suffice. Example 2-11 defines the service metadata and serviceDebug configurations that should apply to all the services. It also shows a JavaScript-enabled configuration that we will apply to our endpoints.

Example 2-11. The behavior configuration for our example application includes service behaviors and endpoint behaviors, enabling integration with ASP.NET AJAX client applications.

<behaviors>
  <serviceBehaviors>
    <behavior name="MetaDataBehavior">
       <serviceMetadata httpGetEnabled="true" />
       <serviceDebug httpHelpPageEnabled="true"
           includeExceptionDetailInFaults="true" />
    </behavior>
  </serviceBehaviors>
  <endpointBehaviors>
    <behavior name="ExampleAjaxBehavior">
      <enableWebScript />
    </behavior>
  </endpointBehaviors>
</behaviors>

After defining the behavior configurations for AJAX services, you can then define the endpoints with a combination of address, binding, and behavior configurations. Example 2-12 shows the behavior configuration for the DataService endpoint using the binding webHttp-Binding and the ExampleAjaxBehavior behavior configuration. Although the service is defined with webHttpBinding, which uses the simple HTTP protocol without SOAP, the service could be exposed at another endpoint with an alternative binding, such as wsHttpBinding, enabling SOAP-based client access.

Example 2-12. The endpoints are defined in web.config by applying behavior configurations, bindings, and addresses for each service endpoint

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<services>
  <service name="KnowledgeBase.DataService"
              behaviorConfiguration="MetaDataBehavior">

    <endpoint address="" behaviorConfiguration="ExampleAjaxBehavior"
              binding="webHttpBinding"
              contract="KnowledgeBase.IDataService" />

    <endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />

  </service>
</services>

The service host file must also be deployed to the Web application to enable the endpoint. Keep in mind that nothing ties the service host file to a particular endpoint configuration; that work is performed through configuration in web.config. When ASP.NET processes the SVC service activation file, it processes the WCF service in an HTTP handler implementation. Here is the service host file for KnowledgeBase.DataService:

<%@ ServiceHost Service="KnowledgeBase.DataService" %>

Note

Note

With WCF applications deployed in ASP.NET, you can also define services inline in the SVC file or in the app_code directory, although this approach isn’t as maintainable or as flexible as keeping your services in an assembly. By implementing your services in an assembly, you can share your services across multiple service-host applications.

WCF Security and Authorization with ASP.NET

When running WCF services in an ASP.NET application with the ASP.NET integrated pipeline (with ASP.NET compatibility enabled), you share ASP.NET authentication, authorization, session, and roles. This means that you use the same ASP.NET authentication mechanisms as in ASP.NET 2.0, although you will usually authenticate the AJAX client through AJAX-enabled Web services rather than a login form. The user is authenticated in the ASP.NET runtime before the WCF service is processed.

Within code running through the ASP.NET pipeline, the user principal is accessed through the HttpContext.Current.User property. HttpContext.Current is always null in a side-by-side hosted WCF application that is not run through the ASP.NET integrated pipeline, as well as in any service exposed outside HTTP. To write code that accesses the user principal in applications that can be hosted both in the ASP.NET pipeline and in pure WCF hosting environments, check for an instance of ServiceSecurityContext through the ServiceSecurityContext.Current property. ServiceSecurityContext will be null in an ASP.NET service, but the HttpContext.Current property is null in a non-ASP.NET WCF service. Example 2-13 demonstrates this technique to get the security principal.

Example 2-13. Checking both ServiceSecurityContext and HttpContext ensures compatible service code in all environments (excerpt from KnowledgeBase/DataService.cs).

protected IPrincipal GetUser()
{
    ServiceSecurityContext sec = ServiceSecurityContext.Current;
    if (sec != null)
        return new GenericPrincipal(sec.PrimaryIdentity, null);
    else if (HttpContext.Current != null)
        return HttpContext.Current.User;
    else
        return null;
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset