Wednesday, April 21, 2010

RIA Service Domain Service Method Life-Cycle

Silverlight Data and Controls Series

Introduction

In this blog I am going to give a short introduction into the methods that can be overwritten when implementing a RIA Service and discuss the execution life-cycle. You will need to get to know these methods if you are going to be writing custom RIA Services that may use existing services that already reside in the enterprise.

Below is some code from the Silverlight blog series I have been working on. I have overridden all of the methods and I am going to discuss the order and purpose of these methods.

namespace MVVM.Web.Service
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using System.Security.Principal;

using Model;

[EnableClientAccess()]
[RequiresAuthentication()]
public class EmployeeDomainService : DomainService
{
UserDTO _user;

public EmployeeDomainService(UserDTO user)
{
_user = user;
}

public override void Initialize(DomainServiceContext context)
{
//load up custom configurations
base.Initialize(context);
}

public override System.Collections.IEnumerable Query(QueryDescription queryDescription, out IEnumerable<ValidationResult> validationErrors, out int totalCount)
{
return base.Query(queryDescription, out validationErrors, out totalCount);
}

public List<EmployeeDTO> GetEmployees() {
List<EmployeeDTO> employees = new List<EmployeeDTO>();

EmployeeDTO employee = new EmployeeDTO();
employee.ID = 1;
employee.FirstName = "Jason";
employee.LastName = "Apergis";
employee.Title = 1;
employee.HireDate = new DateTime(1990, 4, 15);
employees.Add(employee);

employee = new EmployeeDTO();
employee.ID = 2;
employee.FirstName = "Ethan";
employee.LastName = "Apergis";
employee.Title = 2;
employee.HireDate = new DateTime(2000, 6, 3);
employees.Add(employee);

employee = new EmployeeDTO();
employee.ID = 3;
employee.FirstName = "Caroline";
employee.LastName = "Apergis";
employee.Title = 3;
employee.HireDate = new DateTime(2005, 12, 20);
employees.Add(employee);

return employees;
}

public List<TitleDTO> GetTitles() {
List<TitleDTO> titles = new List<TitleDTO>();

TitleDTO title = new TitleDTO();
title.ID = 1;
title.Title = "Developer";
titles.Add(title);

title = new TitleDTO();
title.ID = 2;
title.Title = "Manager";
titles.Add(title);

title = new TitleDTO();
title.ID = 3;
title.Title = "Senior Developer";
titles.Add(title);

title = new TitleDTO();
title.ID = 4;
title.Title = "Senior Manager";
titles.Add(title);

return titles;
}

public override bool Submit(ChangeSet changeSet)
{
return base.Submit(changeSet);
}

protected override bool AuthorizeChangeSet()
{
return base.AuthorizeChangeSet();
}

protected override bool ValidateChangeSet()
{
return base.ValidateChangeSet();
}

protected override bool ExecuteChangeSet()
{
return base.ExecuteChangeSet();
}

private static int count = 10;
public void InsertEmployeeDTO(EmployeeDTO employee) {
//Code to insert employee
employee.ID = count++;
}

public void UpdateEmployeeDTO(EmployeeDTO employee)
{
//Code to update employee
}

public void DeleteEmployeeDTO(EmployeeDTO employee)
{
//Code to delete employee
}

public override object Invoke(InvokeDescription invokeDescription, out IEnumerable<ValidationResult> validationErrors)
{
return base.Invoke(invokeDescription, out validationErrors);
}

protected override bool PersistChangeSet()
{
return base.PersistChangeSet();
}

protected override void OnError(DomainServiceErrorInfo errorInfo)
{
base.OnError(errorInfo);
}
}
}

Query RIA Service Method Life-Cycle

If you are going to run a Load or Invoke from the Silverlight like:

EmployeeDomainContext context = new EmployeeDomainContext();
context.Load<EmployeeDTO>(context.GetEmployeesQuery(), EmployeesLoaded, false);

The following RIA Service methods will be called in the following order:

  1. Constructor – I have constructor because I have implemented a Domain Service Factory which I discussed in this blog.
  2. Initialize – Initialize will always be called and is a good place to do an initialization or loading that would be needed.
  3. Query – This is a method that will be called before any of the query methods are called. This could be a good place to do some logging about the queries being made.
  4. GetEmployees or GetTitles – The query method itself will be called.

Submit RIA Service Method Life-Cycle

Next if the Silverlight application needed to run a Submit method like the following:

public void Save()
{
if (_context.HasChanges)
{
_context.SubmitChanges(new System.Action<System.ServiceModel.DomainServices.Client.SubmitOperation>(SubmitChangesCompleted), null);
}
}

private void SubmitChangesCompleted(System.ServiceModel.DomainServices.Client.SubmitOperation so)
{
RaisePropertyChanged("Employees");
}

The following RIA Service methods will be called in the following order:

  1. Constructor – I have constructor because I have implemented a Domain Service Factory which I discussed in this blog.
  2. Initialize – Initialize will always be called and is a good place to do an initialization or loading that would be needed.
  3. Submit – This is the method that is the starting point for the transaction. Like the Query method, this is a good place to do some logging about the Submit.
  4. AuthorizeChangeSet – This will be called next and will check to see if the user it authorized to submit. I really have not found much information on this method and do not have much reason to override it given I have implemented a custom authentication domain service. I guess you could potentially try to do some form of custom user validation here and if it fails, return a false value. This will stop the reset of the call stack from being executed.
  5. ValidateChangeSet – This next method will be called. All field validations and custom validation classes will be called subsequently called. You can do some logging here. As well if you want to put in logic to potentially fail the service call you can do that as well.
  6. ExecuteChangeSet – This method is the final method that will be called prior to the DTO Insert, Update and Delete methods. If you need to support a transaction, this is the recommended location on where to begin the transaction. It is commonplace to see developers try to begin the transaction in the Submit method but it would be best to wait and see if both authorization and validation succeed before beginning a transaction.
  7. InsertEmployeeDTO – Will be called for each object that has a state of insert insert in the ChangeSet. If there are none, it will not be called.
  8. UpdateEmployeeDTO – Will be called for each object that has a state of update insert in the ChangeSet. If there are none, it will not be called.
  9. DeleteEmployeeDTO – Will be called for each object that has a state of delete insert in the ChangeSet. If there are none, it will not be called.
  10. PersistChangeSet – This will be called after all of the insert, update and deletes have been run. This is where you would commit your transaction.

OnError Method Override

Note the OnError Method Override will be called anytime there is an unhandled error during the execution of the RIA service method call. The best thing you should do here is log the error and let the either the application gracefully handle the error.

References

http://weblogs.asp.net/fredriknormen/archive/2009/12/29/wcf-ria-services-domainservice-life-cycle-and-adding-transactions.aspx

No comments: