Home > Articles > Programming > Windows Programming

📄 Contents

  1. The ASP.NET MVC Framework
  2. Building an MVC RESTful Service-CodeXRC
  3. Where Are We?
This chapter is from the book

Building an MVC RESTful Service—CodeXRC

With this understanding of the basic ASP.NET MVC framework, it’s time to actually build a RESTful service that relies on the framework for basic operations. This service, unlike the blog service in the preceding chapter, won’t create an HttpHandler to service the RESTful requests but will instead be based on controller actions that are activated by the specific service URI mapped in the URL routing table.

To show a service that did more than produce “Hello, World!” I decided to implement the basis of a source code vault, or perhaps a cloud-based file system. The idea behind this is that you select files on your local hard drive and store them in this secured service. This is an aggressive service for the schedule I had to work with, but though it was becoming more complex the further I got into development, I was more convinced it made for a great chapter sample because it addresses many interesting RESTful concepts. These will be evident when I discuss the URI design.

I named the service CodeXRC, the “XRC” part having no particular meaning except it isn’t copyrighted as far as I could tell. (I don’t want to upset the publisher’s legal staff.) The URIs for the service are designed like so:

  • Add/update a project: PUT to /CodeXRC
  • Add/update a project (alternate): PUT to /CodeXRC/{project}
  • Add/update a project folder: PUT to /CodeXRC/{project}/{folder}
  • Add/update a file: PUT to /CodeXRC/{project}/{folder}/{ext}/{file}
  • Delete all projects: DELETE to /CodeXRC
  • Delete a project: DELETE to /CodeXRC/{project}
  • Delete a folder: DELETE to /CodeXRC/{project}/{folder}
  • Delete files by extension: DELETE to /CodeXRC/{project}/{folder}/{ext}
  • Delete a file: DELETE to /CodeXRC/{project}/{folder}/{ext}/{file}
  • Get all projects (high level): GET to /CodeXRC
  • Get a project (all folders/files): GET to /CodeXRC/{project}
  • Get a folder: GET to /CodeXRC/{project}/{folder}
  • Get files by extension: GET to /CodeXRC/{project}/{folder}/{ext}
  • Get a file: GET to /CodeXRC/{project}/{folder}/{ext}/{file}
  • Get statistics/headers: HEAD to a valid service URI

This complex-looking set of URIs really amounts to simulating a file system using REST (see Figure 7.2).

Figure 7.2

Figure 7.2 CodeXRC project storage

The files are stored on the server’s file system in the hierarchy shown in Figure 7.2, but there is also a database associated with the service to maintain ownership of the projects and files as well as foreign key relationships for searching and deletion. Users are registered using a process not provided with the service, but when they’re registered their account information is stored in the typical ASP.NET tables contained within the database. Projects are owned by the registered users and are identified by their user ID, which is a Guid. Roles are also maintained: View, Insert, and Delete. At present the service allows you to access only your own projects, but it would be a relatively easy extension to add View capabilities to projects not your own.

Creating the URL Mapping

With the URI set defined for the service, the place to start is with the URL routing table and mapping. To do this, you open the Global.asax.cs file and look for the RegisterRoutes method. There, you’ll see the default route mapped into the route table (this is created for you by the Visual Studio MVC application wizard). Add this code just before the default route:

// Register the RESTful service URIs...note this *MUST*
// come before the default URI mapping or you’ll sustain
// 404 errors if the URI doesn't match the default
// mapping.
routes.MapRoute(
    "CodeXRC",
    "CodeXRC/{*contentUri}",
    new { controller = "CodeXRC", action = "ServiceRequest" }
);

Of course, this looks a lot like the mapping I discussed previously in the chapter, and given the variable nature of the URI set, you can probably see why I opted for the wildcard approach.

When requests come into the server designated for the CodeXRC service, the CodeXRCController will be called on to take the appropriate action. As is proper for the ASP.NET MVC framework, the CodeXRCController is located in the Web application Controllers folder.

The CodeXRCController

The CodeXRCController contains four actions, one for each HTTP method the service handles: HTTP HEAD, GET, PUT, and DELETE. Each action is adorned with the appropriate AcceptVerbs and ActionName attributes. The HEAD action is shown in Listing 7.1.

Listing 7.1. The CodeXRCController HTTP HEAD action

[AcceptVerbs("HEAD")]
[ActionName("ServiceRequest")]
public ActionResult HeadRequest(string contentUri)
{
    // Filter the query string and remove any trailing '/' so
    // we don't have an extra array element.
    contentUri = FilterUri(contentUri);
    // Save the URI parameters
    string[] directives = null;
    if (!String.IsNullOrEmpty(contentUri))
    {
        directives = contentUri.Split('/');
    }

    // Output to a temporary stream
    MemoryStream strm = new MemoryStream();
    this.HttpContext.Items.Add("OutputStream", strm);

    // Handle the request
    CodeXRCGetService result = new CodeXRCGetService(directives, true);
    return result;
}

In Listing 7.1 you can see that the controller action is named HeadRequest, but because I applied the ActionName attribute, the ASP.NET MVC framework will route the action as if it were named ServiceRequest, which matches the name of the action I registered in Global.asax.cs. And since the AcceptVerbs attribute lists only HTTP HEAD as the accepted HTTP method, this action is called only for HEAD requests.

The parameter contentUri will contain anything on the query string past the controller name. That is, if the client issued a request to the URI

http://servername/AspNetMvcRestService/CodeXRC/Project1/Folder2/cs/CodeFile3

the contentUri parameter would contain the string Project1/Folder2/cs/CodeFile3.

To decide what parameters are present, the contentUri string is split using the slash as the delimiter. However, I filter the string first to remove a trailing slash. This prevents a phantom entry in the resulting string array. That is, this URI would result in three parameters after the string split:

http://servername/AspNetMvcRestService/CodeXRC/Project1/Folder2/

In reality, though, only two parameters are present: Project1 and Folder2. The third entry in the string array would be an empty string, present only because of the trailing slash. By removing the slash, the service doesn’t need to check for empty parameter elements in the parameters string array.

In the case of HTTP HEAD, the service will perform all the normal GET processing. However, the stream the information is written to will differ for the other HTTP methods. For example, GET will use the actual output stream, thus sending a response to the client. HEAD will use a temporary stream, allowing the service to determine the size of the stream so that the appropriate Content-Length header can be returned to the client. The output stream to use is assigned a slot in the HttpContent Items collection so that it won’t need to be passed around as a separate method parameter in all the internal processing methods.

The request is handled by a class that derives from ActionRequest: CodeXRCGetService. As you might imagine, there are corresponding service classes for PUT and DELETE as well. Since GET and HEAD are closely related, a single service class handles both HTTP methods. Something to keep in mind is that the response output stream is not seekable, meaning we can’t query its length. It would have been nice to simply write to the appropriate stream and just query the stream length, but unfortunately the response output stream throws an exception when you access its Length property. This means you have to ask the question “Is this a HEAD request?” If so, then (and only then) should you access the stream’s Length property and create the Content-Length header. I use the stream’s CanSeek property for that.

In contrast, the HTTP PUT method service controller action is shown in Listing 7.2.

Listing 7.2. The CodeXRCController HTTP PUT action

[AcceptVerbs("PUT")]
[ActionName("ServiceRequest")]
public ActionResult PutRequest(string contentUri)
{
    // Filter the query string and remove any trailing '/' so
    // we don't have an extra array element.
    contentUri = FilterUri(contentUri);

    // Save the URI parameters
    string[] directives = null;
    if (!String.IsNullOrEmpty(contentUri))
    {
        directives = contentUri.Split('/');
    }

    // Output to the true response stream
    this.HttpContext.Items.Add("OutputStream",
                 this.HttpContext.Response.OutputStream);

    // Handle the request
    CodeXRCPutService result = new CodeXRCPutService(directives);
    return result;
}

I’ve included the PutRequest method here only to highlight the differences: the HTTP method it accepts is PUT, the controller action is actually ServiceRequest even though the controller method is called PutRequest, the stream to be used for the response is the true output stream (versus a temporary one), and the PUT behavior is exhibited by the CodeXRCPutService class. Otherwise, the request handling for each HTTP method is similar at the controller level.

CodeXRC Service Classes

All the CodeXRC service classes use ActionResult as their base class. In fact, there is a single CodeXRC service base class, CodeXRCServiceBase, that is used to maintain instances of the URI parameters (which I called “directives” because they direct the service), the data access component, and an auxiliary component that sports helper methods for supporting error responses and such. This base class is shown in Listing 7.3.

Listing 7.3. The CodeXRCServiceBase class

public class CodeXRCServiceBase : ActionResult
{
    protected CodeXRCAuxServices _aux = new CodeXRCAuxServices();
    protected CodeXRCDataAccess _dal = new CodeXRCDataAccess();
    protected string[] _directives = new string[0];

    public CodeXRCServiceBase()
    {
    }

    public CodeXRCServiceBase(string[] directives)
    {
        // Later logic depends on this not being
        // null even if it has no elements.
        if (directives != null)
        {
            // Assign value
            _directives = directives;
        }
    }

    public abstract void ExecuteResult(ControllerContext context);
    {
        // Disallow base implementation...note you can't make
        // this abstract since this class is itself derived and
        // this method overridden.
        throw new NotImplementedException(
          "You must override the base ExecuteResult implementation.");
    }
}

Since all the derived classes need to use the data access component, the auxiliary helper method component, and the directives, it made sense to collect that in a base class.

Returning HTML, XML, and JSON

The services themselves are broken out into “get,” “put,” and “delete” versions, each handling the respective HTTP method. Each service method handler also overrides the ActionResult ExecuteResult method. The CodeXRCGetService implementation is shown in Listing 7.4. The other implementations are similar.

Listing 7.4. CodeXRCGetService ExecuteResult method

public override void ExecuteResult(ControllerContext context)
{
    // Test the user's credentials
    if (context.HttpContext.User.IsInRole("View"))
    {
        try
        {
            if (context.HttpContext.Request.AcceptTypes.Count() > 0)
            {
                // Loop through the collection of accepted types.
                // The first one we hit we understand, take it...
                foreach (string type in
                           context.HttpContext.Request.AcceptTypes)
                {
                    switch (type.ToLower())
                    {
                        case "text/html":
                        case "*/*":
                            RenderHtml(context);
                            return;
                        case "text/xml":
                            RenderXml(context);
                            return;

                        case "application/json":
                            RenderJson(context);
                            return;

                        default:
                            // Try next one...
                            break;
                    }
                }

                // Couldn't accept any requested type
                _aux.RenderErrorNotAcceptableGet(context);
            }
            else
            {
                // Couldn't accept any requested type
                _aux.RenderErrorNotAcceptableGet(context);
            }
        }
        catch (Exception ex)
        {
            // Couldn't accept any requested type
            _aux.RenderErrorInternalError(context, ex.ToString());
        }
    }
    else
    {
        // User forbidden
        _aux.RenderErrorForbidden(context);
    }
}

In Listing 7.4 you can see that the code begins by checking the client’s role (the client was authenticated or this method wouldn’t be executing). If the client has the appropriate role credentials, the method then checks the desired return type: HTML, XML, or JSON. The default content type, */*, will return HTML. This allows browsers to view the contents of a project, as shown in Figure 7.3. The chapter’s sample client will always provide the service with an Accept header containing just one accepted content type, but because browsers often include many requested content types, the loop allows the service to check the list of content types for the first one the service has the capability of supporting.

Figure 7.3

Figure 7.3 Browser REST service access

If you use the chapter’s Windows Forms sample client, you can interact with the service in a greater variety of ways, including asking for the project contents as XML, as shown in Figure 7.4.

After the desired return content type is determined, the code in Listing 7.4 then invokes the appropriate rendering method. Listing 7.5 shows you how XML is rendered. I won’t show all the supporting methods because there are many, and they often are recursive (and therefore complex) since the methods often need to traverse directory structures. They’re also not important to demonstrate how to create RESTful services using the ASP.NET MVC framework, so I’ll leave spelunking their inner workings to you. They’re just business logic, so to speak, and as such you’ll find them all in the Models folder of the AspNetMvcRestService Web application.

Figure 7.4

Figure 7.4 CodeXRC project contents as XML

Listing 7.5. The RenderXML method

private void RenderXml(ControllerContext context)
{
    // Pull the user's ID. This works here since we're using the
    // SQL-based membership provider.
    MembershipUser user =
      Membership.GetUser(context.HttpContext.User.Identity.Name);
    Guid ownerID = (Guid)user.ProviderUserKey;

    context.HttpContext.Response.ContentType = "text/xml";
    switch (_directives.Count())
    {
        case 0:
        default:
            // List all projects for user:
            // http://{host}/{vdir}/CodeXRC
            RenderXmlProjectList(context, ownerID);
            break;

        case 1:
            // List specific project for user:
            // http://{host}/{vdir}/CodeXRC/{project}
            RenderXmlProject(context, ownerID, _directives[0]);
            break;

        case 2:
            // List specific folder for user:
            // http://{host}/{vdir}/CodeXRC/{project}/{folder}
            RenderXmlFolder(context, ownerID, _directives[0],
              _directives[1]);
            break;

        case 3:
            // List files by extension for user:
            // http://{host}/{vdir}/CodeXRC/{project}/{folder}/{ext}
            RenderXmlFilesByType(context, ownerID, _directives[0],
              _directives[1], _directives[2]);
            break;

        case 4:
            // Return specific file to user:
            // http://{host}/{vdir}/CodeXRC/{proj}/{folder}/{ext}/{file}
            RenderXmlFileByName(context, ownerID, _directives[0],
              _directives[1], _directives[2], _directives[3]);
            break;
    }

    // Assign the content length, but only if HEAD.
    Stream stream = context.HttpContext.Items["OutputStream"] as Stream;
    if (stream != null && stream.CanSeek)
    {
        context.HttpContext.Response.Headers["Content-Length"] =
          GetContentLength(context).ToString();
    }
}

The methods to render HTML and JSON are similar. Actually, rendering XML and JSON is easy using the DataContractSerializer and DataContractJsonSerializer objects, respectively. But the HTML is rendered by hand (again, one could perhaps use a view for this or augment the XML output with an XSLT reference, but I chose to keep the service logically contained, for right or wrong). After the content is rendered, the stream is examined for its length if the HTTP method was HEAD.

An important aspect of returning a resource representation is creating the links (remember hypermedia as the engine of application state). When representations are created, a special class is used to generate the appropriate links, which are shown in Listing 7.6.

Listing 7.6. The CodeXRC link builder class

public static class CodeXRCLinkBuilder
{
    public static string BuildProjectLink(ControllerContext context,
                                          string projectName)
    {
        // Generate the link (remember HATEOAS)
        string fullUrl = context.HttpContext.Request.Url.ToString();
        string baseUrl = fullUrl.Substring(0, fullUrl.IndexOf(
          context.HttpContext.Request.ApplicationPath));
        return String.Format("{0}{1}/CodeXRC/{2}", baseUrl,
          context.HttpContext.Request.ApplicationPath, projectName);
    }

    public static string BuildFolderLink(ControllerContext context,
                                         string projectName,
                                         string folderName)
    {
        // Generate the link (remember HATEOAS)
        string fullUrl = context.HttpContext.Request.Url.ToString();
        string baseUrl = fullUrl.Substring(0, fullUrl.IndexOf(
          context.HttpContext.Request.ApplicationPath));
        return String.Format("{0}{1}/CodeXRC/{2}/{3}", baseUrl,
          context.HttpContext.Request.ApplicationPath,
          projectName, folderName);
    }

    public static string BuildFileLink(ControllerContext context,
                                       string projectName,
                                       string folderName,
                                       string fileExtension,
                                       string fileName)
    {
        // Generate the link (remember HATEOAS)
        string fullUrl = context.HttpContext.Request.Url.ToString();
        string baseUrl = fullUrl.Substring(0, fullUrl.IndexOf(
            context.HttpContext.Request.ApplicationPath));
        return String.Format("{0}{1}/CodeXRC/{2}/{3}/{4}/{5}", baseUrl,
          context.HttpContext.Request.ApplicationPath, projectName,
          folderName, fileExtension, fileName);
    }
}

The link builder class simply encapsulates the logic in a single place that’s necessary to build valid URIs for the various resources.

Creating Resources with XML

The only representation the CodeXRC service accepts for creating new resources is XML, and the CodeXRCPutService ExecuteResult method code is shown in Listing 7.7.

Listing 7.7. CodeXRCPutService ExecuteResult method

public override void ExecuteResult(ControllerContext context)
{
    // Test the user's credentials
    if (context.HttpContext.User.IsInRole("Insert"))
    {
        // We'll accept only XML for creating/updating a
        // resource. We could accept JSON easily, but this
        // is how you'd limit inputs to a specific type.
        if (context.HttpContext.Request.ContentType.ToLower() ==
              "text/xml")
        {
            // Create the indicated resource
            CreateResource(context);
        }
        else
        {
            // Wasn't XML...
            _aux.RenderErrorNotAcceptablePut(context);
        }
    }
    else
    {
        // User forbidden
        _aux.RenderErrorForbidden(context);
    }
}

The ExecuteResult in this case checks for the appropriate role as well as XML as the content type, but only to return an error if the indicated type isn’t appropriate. Therefore, there is only one “create resource” method, which is shown in Listing 7.8.

Listing 7.8. CodeXRCPutService CreateResource method

private void CreateResource(ControllerContext context)
{
    // Pull the user's ID. This works here since we're using
    // the SQL-based membership provider.
    MembershipUser user =
      Membership.GetUser(context.HttpContext.User.Identity.Name);
    Guid ownerID = (Guid)user.ProviderUserKey;

    context.HttpContext.Response.ContentType = "text/html";
    switch (_directives.Count())
    {
        case 0:
        default:
            // Create a project for user (will be a specific project):
            // http://{host}/{vdir}/CodeXRC
            CreateProject(context, ownerID);
            break;

        case 1:
            // Create specific project for user (all child folders
            // and files):
            // http://{host}/{vdir}/CodeXRC/{project}
            CreateProject(context, ownerID, _directives[0]);
            break;

        case 2:
            // Delete specific folder (all child folders and files):
            // http://{host}/{vdir}/CodeXRC/{project}/{folder}
            CreateFolder(context, ownerID, _directives[0],
              _directives[1]);
            break;

        case 3:
            // Delete files by extension in specified folder:
            // http://{host}/{vdir}/CodeXRC/{project}/{folder}/{ext}
            CreateFileByType(context, ownerID, _directives[0],
              _directives[1], _directives[2]);
            break;

        case 4:
            // Delete specific file:
            // http://{host}/{vdir}/CodeXRC/{proj}/{folder}/{ext}/{file}
            CreateFile(context, ownerID, _directives[0], _directives[1],
              _directives[3] + "." + _directives[2]);
            break;
    }
}

The code in Listing 7.8 is somewhat similar to the code for retrieving a resource representation in Listing 7.5. The URI is examined and the appropriate resource is created. Of course, there is no need to return a content length, so that code is missing from Listing 7.8. The code to handle HTTP DELETE is similar to the code shown in Listing 7.8, so I won’t show that here either.

Returning Error Information

In the preceding chapter, error information was returned according to the application’s error handling configuration. For this service, I chose to return explicit error information as HTML I create at the point where the exception is caught. The CodeXRCAuxServices class provides several error response methods, with an example being the HTTP 403 (Forbidden) response shown in Listing 7.9.

Listing 7.9. The CodeXRCAuxServices RenderErrorForbidden method

public void RenderErrorForbidden(ControllerContext context)
{
    context.HttpContext.Response.StatusCode =
      (Int32)HttpStatusCode.Forbidden;
    context.HttpContext.Response.ContentType = "text/html";
    using (StreamWriter wtr =
    new StreamWriter((Stream)context.HttpContext.Items["OutputStream"]))
    {
        StringBuilder sb = new StringBuilder();
        sb.Append("<html><head><title>CodeXRC</title></head><body>");
        sb.Append("You are not authorized to access the resource you ");
        sb.Append("requested.<br/></body><html>");
        wtr.WriteLine(sb.ToString());
        wtr.Flush();
        wtr.Close();
    }
}

Other errors are handled in a similar manner.

The CodeXRC Data Access Layer

The service’s data access is provided by the CodeXRCDataAccess class. I personally generally prefer to provide data access service through an interface and use a strategy pattern to load the appropriate data access component, but here I keep the example simple and just deal with data access directly.

Listing 7.10 contains the data access code used to read the database and return all the user’s projects.

Listing 7.10. The CodeXRCDataAccess ListProjects method

public List<ProjectDTO>
  ListProjects(ControllerContext context, Guid ownerID)
{
    // Retrieve all projects
    CodeXRCDataClassesDataContext dataContext =
      new CodeXRCDataClassesDataContext();
    var projectQuery = from p in dataContext.Projects
         orderby p.ProjectID
         where p.OwnerID == ownerID
         select p;
    List<ProjectDTO> projects = new List<ProjectDTO>();
    foreach (var project in projectQuery)
    {
        // Create the new project item
        ProjectDTO projectItem = new ProjectDTO(project);
        projectItem.Link = CodeXRCLinkBuilder.BuildProjectLink(
          context, project.DisplayName);
        projects.Add(projectItem);
    }
    return projects;
}

As you can see from Listing 7.10, I make heavy use of LINQ to SQL, and you’ll find this throughout the data access code. The LINQ to SQL context is shown in Figure 7.5.

Figure 7.5

Figure 7.5 The CodeXRC LINQ data context

The foreign keys shown in Figure 7.5 make it easier to query for specific entities. The data access code also creates the folders and files, and when it does, it stores the local directory or file paths in the LocalStorage column for later recall. The service stores all folders and files within the App_Data subdirectory, but you could easily change this if you prefer. I selected it simply because the service didn’t require any changes to file system permissions to read, write, and delete files in that location.

The data is conveyed to the client using data transfer objects (DTO). I’ve shown the ProjectDTO in Listing 7.11, but the other DTOs are similar.

Listing 7.11. The project data transfer object

[DataContract(Namespace = "http://www.endurasoft.com/REST.NET/", Name="Project")]
public class ProjectDTO
{
    public ProjectDTO()
    {
    }

    public ProjectDTO(Project dbProject) :
        this()
    {
        // Copy properties
        this.ProjectID = dbProject.ProjectID;
        this.DisplayName = dbProject.DisplayName;
    }

    public ProjectDTO(Project dbProject, FolderDTO[] folders) :
        this(dbProject)
    {
        // Copy folders
        this.Folders = folders;
    }

    [DataMember]
    public Int32 ProjectID { get; set; }

    [DataMember]
    public string DisplayName { get; set; }

    [DataMember(IsRequired = false)]
    public string Link { get; set; }

    [DataMember(IsRequired = false)]
    public FolderDTO[] Folders { get; set; }
}

Each DTO provides several constructors, including a copy constructor that accepts the corresponding LINQ entity. (Note that another way to approach this is to create extension methods and keep the DTOs in a separate assembly, which has advantages from a code-separation standpoint, and the DTOs wouldn’t have to reference LINQ.) The client has similar DTOs I created using the same technique described in the preceding chapter—I queried the service for the appropriate XML and used xsd.exe to create a corresponding C# class. I then removed the XmlSerializer attributes and replaced them with DataContractSerializer attributes. This is cheating a little since I had unfair knowledge as to how the XML was generated, but you could use either serializer as long as you faithfully re-create the XML the service requires.

The CodeXRC Service Client

Even though this service will return HTML, to demonstrate creation and deletion of projects, folders, and files, I created a Windows Forms client you can use to experiment with the various aspects of the service. The client won’t exercise all aspects of the service. It only adds projects, whereas the service will accept folders and files as well (but keep in mind you’ll need to provide correct foreign keys, which is typical of many RESTful services when updating resources).

The basic user interface you’ve already seen in Figure 7.4. The client also uses a similar authentication dialog box as the preceding chapter’s client, so I won’t repeat that as well. However, adding a new project does merit some description. The dialog box for this is shown in Figure 7.6.

Figure 7.6

Figure 7.6 The CodeXRC client Create Project dialog box

The Create Project dialog box appears only when XML is selected as the content type, PUT is selected as the HTTP method, and you click the Execute button. Other content types for PUT display an error dialog box; and other HTTP methods don’t involve resource creation, so no error is necessary.

After you provide a project name, you can select folders or files. If you choose to add folders, you’ll be presented with the dialog box shown in Figure 7.7.

Figure 7.7

Figure 7.7 The CodeXRC client folder browser dialog box

After you select a folder, the child folders and files will be added to the tree view control shown in the Create Project dialog box. From this tree control you can remove files and folders simply by selecting them and pressing the Delete key.

If you instead decide to add files directly to the project, the client creates a virtual folder, with the same name as the project, and adds the files to that. The service doesn’t add files directly to a project, but this is simply an implementation detail you could change fairly easily if you wanted. I kept this as a business rule simply to have the rule in play. My reasoning was that projects and folders aren’t the same in the service I implemented even though a project is implemented as a folder on disk. Figure 7.8 shows the Create Project dialog box when files are added to the project directly.

Figure 7.8

Figure 7.8 Adding files directly to a project

In all cases the files are read as binary files and converted into Base64 strings for transmission over the network. Base64, on average, introduces a 30% (or so) increase in size, but it’s the only safe way to send binary information over a network using XML. The client will convert the files into Base64 and the service will convert them back into their original binary form for storage; but the client is limited in the sense that it cannot recall the files. I’ll leave the implementation of that to you to complete, but the basic conversion logic is already present in the service itself. The service will return to the client the XML representation of a created project, but this is so you could cache the foreign keys if you chose to do so. The contents of the files are wiped from the representation to conserve bandwidth.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020