SharePoint Design Patterns:
Here are the few design patterns that can be used when creating custom designs,
- Repository
- MVP
- Service Locator
Repository Pattern:
Context: In many applications in SharePoint, the business logic accesses data sources such as SqlServer, SharePoint List, and Web services. Directly accessing the data source can result the following:
- Duplicate Code
- A higher potential for programming errors
- Week Typing of business data
- Difficulty to centralizing data related policies
Objective:
Use the Repository pattern to achieve one or more of the following objectives:
- You want to maximize the amount of code that can be tested with automation and to isolate the data layer to support unit testing.
- You access the data source from many locations and want to apply centrally managed, consistent access rules and logic.
- You want to implement and centralize a caching strategy for the data source.
- You want to improve the code's maintainability and readability by separating business logic from data or service access logic.
- You want to use business entities that are strongly typed so that you can identify problems at compile time instead of at run time.
- You want to associate a behavior with the related data. For example, you want to calculate fields or enforce complex relationships or business rules between the data elements within an entity.
- You want to apply a domain model to simplify complex business logic.
Solution:
Use a repository to separate the logic that retrieves the data and maps it to the entity model from the business logic that acts on the model. The business logic should be agnostic to the type of data that comprises the data source layer. For example, the data source layer can be a database, a SharePoint list, or a Web service.The repository mediates between the data source layer and the business layers of the application. It queries the data source for the data, maps the data from the data source to a business entity, and persists changes in the business entity to the data source. A repository separates the business logic from the interactions with the underlying data source or Web service. The separation between the data and business tiers has three benefits:
- It centralizes the data logic or Web service access logic.
- It provides a substitution point for the unit tests.
- It provides a flexible architecture that can be adapted as the overall design of the application evolves.
Interactions of the repository
The client submits new or changed entities to the repository for persistence. In more complex situations, the client business logic can use the Unit of Work pattern. This pattern demonstrates how to encapsulate several related operations that should be consistent with each other or that have related dependencies. The encapsulated items are sent to the repository for update or delete actions. This guidance does not include an example of the Unit of Work pattern. For more information, see Unit of Work on Martin Fowler's Web site.
Repositories are bridges between data and operations that are in different domains. A common case is mapping from a domain where data is weakly typed, such as a database or SharePoint list, into a domain where objects are strongly typed, such as a domain entity model. One example is a database that uses IDbCommand objects to execute queries and returns IDataReader objects. Another example is SharePoint, which uses SPQuery objects to return SPListItemcollections. A repository issues the appropriate queries to the data source, and then it maps the result sets to the externally exposed business entities. Repositories often use the Data Mapper pattern to translate between representations. Repositories remove dependencies that the calling clients have on specific technologies. For example, if a client calls a catalog repository to retrieve some product data, it only needs to use the catalog repository interface. For example, the client does not need to know if the product information is retrieved with SQL queries to a database or Collaborative Application Markup Language (CAML) queries to a SharePoint list. Isolating these types of dependences provides flexibility to evolve implementations.
SharePoint List Repositories
The following diagram illustrates the interactions of a SharePoint list repository with SharePoint lists and the business logic.
Interactions of a SharePoint list repository
Using the Repository pattern in a SharePoint application addresses several concerns.
- SharePoint applications often store business information in SharePoint lists. To retrieve data from SharePoint lists requires careful use of the SharePoint API, knowledge of the GUIDs that are related to the lists and their fields, and a working knowledge of CAML. Repositories centralize this logic.
- The amount of code that is required to query or update a SharePoint list item is enough to warrant its encapsulation into helper methods. When Web Forms, event receivers, and workflow business logic all require access to the same lists, the code that accesses the SharePoint lists can be duplicated throughout the application. This can make the application prone to bugs and difficult to maintain. Repositories eliminate this duplication.
- Without a repository, the application is difficult to unit test because the business logic has direct dependencies on the SharePoint lists. Repositories centralize the access logic and provide a substitution point for the unit tests.
The following sections show how the repository pattern is implemented in the SharePoint Guidance Library. For more information, see List-Based Repositories.
SharePoint Guidance Library Helper Classes
The following diagram shows the major components of a SharePoint list repository.
Components of a SharePoint list repository
The list repository contains a query object and a data mapper object that are specific to SharePoint. These are theListItemFieldMapper and CAMLQueryBuilder classes. The data mapper translates between an SPListItem and the business entity that is defined by the application. The query object internally constructs an SPQuery object and uses CAML to query the list.
Implementation Variations
When you create a SharePoint list repository, you should consider how the repository locates the list that it is going to access. A list typically resides in a site, and it can be accessed either through its Uniform Resource Identifier (URI) or its GUID. The repository needs one of these, but passing this information to a repository can be challenging if you use the repository in conjunction with a service location. For more information, see The Service Locator Pattern.There are three ways in which a repository can access a list:
- A list can be centrally located. In this case, a repository is associated with a list that is at a fixed location. All sites retrieve the data from this central location. The PartnerPromotionsRepository class in the Partner Portal application is an example of a repository that uses such as list.
- A list can be accessed relative to the current site context. In this case, a repository is associated with a list whose location is relative to the current site. The IncidentManagementRepository class in the Partner Portal application is an example of such a repository.
- A list can be accessed according to a context that is supplied by a consumer. In this case, only the consumer of the repository knows which list the repository should access. There is no example of this in the Partner Portal application. However, you can extend the Training Management application to support this use case. You can implement a repository that accesses the list of training courses for your department on your local installation and also accesses related training courses that are located on another departmental site. In this case, the consumer instructs the repository to target a particular list.
Lists That Are Centrally Accessed from a Fixed Location
In this case, a list is at a fixed location, and all sites access it from this central point. Its location cannot be determined based on the current context. Although it is possible to hard code the location of the list, this is not recommended, because the topology of the site can change. It is often better to make the location of the list a configuration setting. In that case, defining the list's location is an administrative task. The location is established when the site topology is set up.For example, the Partner Portal application centrally manages the published promotions for all partners by locating them on one site collection. Partners see their particular promotions on their collaboration home pages. Each partner collaboration site is hosted in its own site collection. This establishes security boundaries and isolates the data intended for one partner from the data intended for another partner. Because the relationship between the list and consumers of the list is based on the operational topology, the list location is defined with configuration data.
The following are characteristics of a list with a fixed location:
- There is typically only one instance of that list within the Web application scope.
- The location of the list is determined when the site topology is designed or when the site is installed.
- The list location should be retrieved from configuration data that is shared by all consumers. This data is typically at the Web application level or Web farm level.
Associating a repository with a list at a central location
The service locator constructs a repository object, which then reads the configuration information. The repository accesses the list based on this data. Because the repository relies on the configuration data, it can be constructed independently of the application context. It does not need any additional information from the list consumers.
This approach is susceptible to run-time exceptions because it depends on configuration data, which can be erroneous, lost, or corrupted. Make sure that you provide adequate diagnostics that inform IT administrators of any configuration errors. Problems that are caused by configuration errors are difficult to resolve without adequate logging information.
Lists with a Location That Is Fixed, Relative to the Current Context
In this case, the repository is associated with a list whose location is fixed, relative to the current context. For example, in the Training Management application, registration and course lists are located at the same relative location within a site. However, there can be several Training Management sites within a SharePoint farm. In this situation, the list repository is loaded from the current context.The following are characteristics of a list with a location that is relative to the context:
- The list has a fixed location that is relative to a site (an SPWeb object).
- The location is independent of the site topology.
- The list location is based on the current SharePoint context.
Associating a repository with a list whose location is relative to the context
SharePoint often has a number of instances of the same Web application. In this situation, the repository gets the current site from the SharePoint context (the SPContext.Current.Web object) and loads the list information from this context. Because this relationship is fixed relative to the current site, the repository needs no additional information. The repository instance can be directly constructed by the service locator.
Lists Whose Context Is Supplied by a Consumer
In this case, the repository is associated with a particular content type and can access any list that has SPListItems of this content type. The consumer must provide context to the repository. For example, the consumer might provide theSPWeb object that holds the list or the GUID of the list. Although it is generally a good practice to keep technology-specific dependencies (such as a reliance on SQL Server) out of the repository interface, providing context when the repository is constructed is an accepted, widely used practice.This scenario occurs with sites that have a dynamic topology, or where relationships are established by a user who supplies configuration information. If you add or remove sites at run time that contain lists that the repository accesses, you often have to provide the context. An example is if you use the FINANCE training site to view courses but you also want to see the courses on the Human Resources training site.
To provide this capability, you can build a general purpose Web Part to view the courses from other departmental training sites. You can add this Web Part to the FINANCE training site and configure it to view the related Human Resources department courses. The repository that the Web Part on the Finance site uses receives the location of the Human Resources course list as context information when the Web Part constructs it.
One challenge with this type of repository is using it in combination with the SharePoint Guidance Library service locator. The ActivatingServiceLocator class can only use parameterless constructors for the repositories. It is not possible to pass the contextual information (in this case, the location of the list) into the repository through the constructor. One way to solve this is to pass the location of the list with each method call, but this inserts a dependency on the list's URL into the interface definition. The Training Management application uses this approach.
A better, but more complicated way to pass the location of the list to the repository is to use a factory. The factory includes a method that creates the repository. The consumer passes the location of the list to the method. The consumer then uses the ActivatingServiceLocator to access the factory and uses it to create the repository. With this approach, the consumer provides the location of the list to the factory, which in turn creates the repository. The factory passes the context through the repository constructor. This technique is known as constructor injection.
The following are characteristics of a list whose context is supplied by a consumer:
- The consumer can determine which list the repository should access.
- The location of the list is often determined at run time.
- This list location is derived from the current business context.
Associating a repository with a list whose context is supplied by the consumer
The consumer constructs the context for the repository. The consumer retrieves an instance of a repository factory from the service locator. The consumer then uses the repository factory to construct the repository. The consumer provides the context for the list. The repository uses this information to locate the list. Because the repository is decoupled from both the configuration data and the context, it is suitable for many scenarios. However, because the consumer provides the context, it increases the coupling between the consuming code and the repository.
Web Service Repositories
A common backing store for data is a business service that is exposed by a line-of-business (LOB) application. Generally, these business services are at a higher level of abstraction than the standard Create/Read/Update/Delete (CRUD) semantics of a database or SharePoint list. However, from the perspective of the client, they often are equivalent to a data source. Like with SharePoint lists, accessing Web services can be complex and prone to error. A repository centralizes the access logic for a service and provides a substitution point for unit tests. Note that services are often expensive to invoke and benefit from caching strategies that are implemented within the repository.The following diagram shows a service back-end repository that uses caching.
Using a repository with a Web service
In this case, the query logic in the repository first checks to see whether the queried items are in the cache. If they are not, the repository accesses the Web service to retrieve the information. Although it is possible to access services directly, it is also possible to access them through the SharePoint Business Data Catalog (BDC). The BDC can aggregate several data sources, including Web services, and expose them through a uniform, generic interface. The BDC allows you to use standard Web Parts to display and modify data. For more information, see Consuming Web Services with the Business Data Catalog (BDC).
You may need more complex security options than the BDC supports. In this situation, you can use the Windows Communication Foundation (WCF). This requires that your own code and configuration data manage the service information and security context. For more information, see Integrating Line-of-Business Systems.
Repository Examples
For an example of the list repository pattern, see Development How-to Topics. Also, the Partner Portal application includes the following list repositories that can be used as starting points:- The Partner Promotion Repository is in the PartnerPromotionRepository.cs file of the PartnerPortal\Contoso.PartnerPortal.Promotions directory. There is also a mock implementation for unit testing in the PartnerPromotionsPresenterFixture.cs file of the PartnerPortal\Contoso.PartnerPortal.Promotions.Tests directory.
- The Business Event Type Configuration Repository is in the BusinessEventTypeConfigurationRepository.cs file of the Microsoft.Practices.SPG2\Microsoft.Practices.SPG.SubSiteCreation\BusinessEventTypeConfiguration directory. There is also a mock implementation for unit testing in the ResolveSiteTemplateFixture.cs file of the Microsoft.Practices.SPG2\Microsoft.Practices.SPG.SubSiteCreation.Tests directory.
- The Subsite Creation Requests Repository is in the SubSiteCreationRequestsRepository.cs file of the directory Microsoft.Practices.SPG2\Microsoft.Practices.SPG.SubSiteCreation\SubSiteCreationRequests.
- The Incident Management Repository is in the IncidentManagementRepository.cs file of the directory PartnerPortal\Contoso.LOB.Services.Client\Repositories.
- The Pricing Repository is in the PricingRepository.cs file of the directory PartnerPortal\Contoso.LOB.Services.Client\Repositories.
- The Cached BDC Product Catalog Repository is in the CachedBdcProductCatalogRepository.cs file of the directory PartnerPortal\Contoso.LOB.Services.Client\Repositories. There is also a mock implementation for unit testing in the ProductDetailsPresenterFixture.cs file of the directory PartnerPortal\Contoso.PartnerPortal.ProductCatalog.Tests.
- The Full Text Search IncidentTask Repository uses SharePoint Search as its data source. This repository is found in the FullTextSearchIncidentTaskRepository.cs file of the directory PartnerPortal\Contoso.PartnerPortal.Collaboration.Incident\Repositories.
- The Partner Site Directory uses the site directory list to provide the Partner site collection URL and the user profile to provide the PartnerID. The repository is implemented in the PartnerSiteDirectory.cs file of the directory PartnerPortal\Contoso.PartnerPortal.PartnerDirectory.
Considerations
The Repository pattern increases the level of abstraction in your code. This may make the code more difficult to understand for developers who are unfamiliar with the pattern. Although implementing the pattern reduces the amount of redundant code, it generally increases the number of classes that must be maintained.The Repository pattern helps to isolate both the service and the list access code. Isolation makes it easier to treat them as independent services and to replace them with mock objects in unit tests. Typically, it is difficult to unit test the repositories themselves, so it is often better to write integration tests for them.
When caching data in a multithreaded environment, consider synchronizing access to the cache in addition to the cached objects. Often, common caches, such as the ASP.NET cache, are already thread safe, but you must also ensure that the objects themselves can operate in a multithreaded environment.
If you are caching data in heavily loaded systems, performance can be an issue. Consider synchronizing access to the data source. This ensures that only a single request for the data is issued to the list or back-end service. All other clients rely on the retrieved data. For more information, see Techniques for Aggregating List and Site Information.
Service Locator Pattern
Context: You have classes with dependencies on services whose concrete types are specified at compile time. In the following example, ClassA has compile time dependencies on ServiceA and ServiceB. The following diagram illustrates this.
Classes with dependencies on services
This situation has the following drawbacks:
- To replace or update the dependencies, you must change your classes' source code and recompile the solution.
- The concrete implementation of the dependencies must be available at compile time.
- Your classes are difficult to test in isolation because they have a direct reference to their dependencies. This means that these dependencies cannot be replaced with stubs or mock objects.
- Your classes contain repetitive code for creating, locating, and managing their dependencies.
The next section describes how to address these issues.
Objective:
Use the Service Locator pattern to achieve any of the following objectives:
- You want to decouple your classes from their dependencies so that these dependencies can be replaced or updated with little or no change to the classes.
- You want to write logic that depends on classes whose concrete implementation is not known at compile time.
- You want to be able to test your classes in isolation, without the dependencies.
- You do not want the logic that locates and manages the dependencies to be in your classes.
- You want to divide your application into loosely coupled modules that can be independently developed, tested, versioned, and deployed.
Solution:
Create a service locator that contains references to the services and that encapsulates the logic that locates them. In your classes, use the service locator to obtain service instances. The following diagram illustrates how classes use a service locator.
How classes use a service locator
The Service Locator pattern does not describe how to instantiate the services. It describes a way to register services and locate them. Typically, the Service Locator pattern is combined with the Factory pattern and/or the Dependency Injection pattern. This combination allows a service locator to create instances of services.
Implementation Details
The SharePoint Guidance Library offers an implementation of the Service Locator pattern. TheSharePointServiceLocator class provides access to a singleton IServiceLocator instance and manages that instance. The SharePointServiceLocator class includes a default implementation of the interface. This is theActivatingServiceLocator class. This class can both create and locate services.The Partner Portal application shows how to use the Service Locator to register and locate services such as repositories, logging services, and configuration management services. For more information, see The SharePoint Service Locator.
Considerations
Consider the following points before you use the Service Locator pattern:- There are more solution elements to manage.
- You must write additional code that adds service references to the service locator before your objects can use it.
- Your classes have a dependency on the service locator.
- The source code is more complex and difficult to understand.
- You can use configuration data to define run-time relationships.
- You must provide implementations of the services. Because the Service Locator pattern decouples service consumers from service providers, it might be necessary to provide additional logic. This logic ensures that the service providers are installed and registered before service consumers try to locate them.
I. When to use MVP Pattern?
· You need isolate your business logic from the details of your user interface UI
· You want to separate business logic from user interface UI logic to make the code easier to maintain and understand
· When you need to reduce the complex of the WebPart that handles user events, retrieves data, modify other controls on the page in response to the events, and submits the changed data. MVP pattern helps you for maintain and test the functionality
· You want to maximize the amount of code that can be tested with automation, because the views are difficult to test
· The use of the MVP pattern increases modularity, flexibility, and testability of the application
II. How to use MVP with WebParts?
In order to implement this pattern, complete the follow tasks:
1. Create two classes a view and a presenter for separate the logic for the visual display from the event handling behavior.
2. The view (In this case is a Web page or a Web Part) manages the controls on the Web page and forwards user events to a presenter.
3. The presenter contains the logic to respond to the events, updates the model (both the business logic and the application data), and alters the state of the view.
4. Define a view interface, for to make the presenter testable. This interface has the presenter refer to the view interface instead of to the view implementation class. This allows you to replace the actual view with a substitute implementation for unit tests.
5. Implement this model:
The follow table shows this classes and each responsabilities:
2. The view (In this case is a Web page or a Web Part) manages the controls on the Web page and forwards user events to a presenter.
3. The presenter contains the logic to respond to the events, updates the model (both the business logic and the application data), and alters the state of the view.
4. Define a view interface, for to make the presenter testable. This interface has the presenter refer to the view interface instead of to the view implementation class. This allows you to replace the actual view with a substitute implementation for unit tests.
5. Implement this model:
The follow table shows this classes and each responsabilities:
III. Implementation
IAggregateView
public interface IAggregateView
{
DataTable SetSiteData { set; }
}
public interface IAggregateView
{
DataTable SetSiteData { set; }
}
AggregateView
public class AggregateView : WebPart, IAggregateView
{
private AggregateViewPresenter presenter;
protected override void CreateChildControls()
{
base.CreateChildControls();
// Configure the grid view.
presenter = new AggregateViewPresenter(this, new ProductService());
presenter.SetSiteData();
Controls.Add(gridView);
IErrorVisualizer errorVisualizer = new ErrorVisualizer(this);
presenter.ErrorVisualizer = errorVisualizer;
}
}
public class AggregateView : WebPart, IAggregateView
{
private AggregateViewPresenter presenter;
protected override void CreateChildControls()
{
base.CreateChildControls();
// Configure the grid view.
presenter = new AggregateViewPresenter(this, new ProductService());
presenter.SetSiteData();
Controls.Add(gridView);
IErrorVisualizer errorVisualizer = new ErrorVisualizer(this);
presenter.ErrorVisualizer = errorVisualizer;
}
}
AggregateViewPresenter
public class AggregateViewPresenter
{
private IAggregateView view;
private IProductService productService;
public AggregateViewPresenter(IAggregateView view, IProductService productService)
{
this.view = view;
this.productService = productService;
}
}
public class AggregateViewPresenter
{
private IAggregateView view;
private IProductService productService;
public AggregateViewPresenter(IAggregateView view, IProductService productService)
{
this.view = view;
this.productService = productService;
}
}
IProductService
public interface IProductService
{
DataTable GetSiteData();
}
public interface IProductService
{
DataTable GetSiteData();
}
ProductService
public class ProductService : IProductService
{
private SPSiteDataQuery query;
public ProductService()
{
query = new SPSiteDataQuery();
query.Lists = "";
query.ViewFields = "" + "";
query.Query = "";
query.Webs = "";
}
public System.Data.DataTable GetSiteData()
{
SPWeb web = SPContext.Current.Web;
return web.GetSiteData(query);
}
}
public class ProductService : IProductService
{
private SPSiteDataQuery query;
public ProductService()
{
query = new SPSiteDataQuery();
query.Lists = "";
query.ViewFields = "" + "";
query.Query = "";
query.Webs = "";
}
public System.Data.DataTable GetSiteData()
{
SPWeb web = SPContext.Current.Web;
return web.GetSiteData(query);
}
}
IV. Considerations
· If you want to display the data differently, you can modify or replace the view, without changing any of the business logic, by providing an alternative implementation of IAggregateView.
· You can create a view that displays the data in any way you want, as long as it exposes a public write-only property of type DataTable named SetSiteData.
· If you change the way you store your Products, you can provide an alternative implementation of IProductService without editing the view or the presenter.
· The design makes it easy to test your presenter logic by providing mock implementations of IProductService and IAggregateView.
· The view is relatively passive and is entirely driven by the presenter logic.
· The view class simply provides a forward-only property setter that the presenter can use to set the data source for the view.
No comments:
Post a Comment