A Prism 4 Application Checklist
Introduction
This article was written as a companion piece to Creating View-Switching Applications with Prism 4, also published on CodeProject. However, it can be used as a guide to setting up almost any Prism 4 application. The article provides a step-by-step explanation of how to set up a Prism application.
The checklist in this article assumes that the developer will use Unity 2.0 as the Dependency Injection (DI) container for the application, and that logging will be performed using the log4net framework. But the checklist does not depend heavily on these components, and it should be easily adaptable for use with other DI containers and logging frameworks.
The checklist includes the steps needed to set up a Prism application for View-switching, and the demo app included with the article is a View-switching app. For more information on View-switching, see the companion article. Note that the demo app does not implement logging.
Step 1: Create a Prism Solution
The first step in setting up a Prism application is to create and configure the solution and Shell project.
Step 1a – Create a solution: Create a new WPF project, making sure that the Solution drop-down in the New Project dialog is set to ‘Create new solution’. The Name field should contain the name for your solution. If you are going to use the WPF Ribbon in your app, be sure to select WPF Ribbon Application as the project type, since its window differs somewhat from the standard WPF window.
Step 1b – Add DLLs to the solution library: Create a folder at the root level of your solution called [Solution Name].Library. In that folder, create a subfolder named Prism. Add the following DLLs to that folder:
- Microsoft.Practices.Prism.dll (and the corresponding XML file)
- Microsoft.Practices.Prism.UnityExtensions.dll (and the corresponding XML file)
- Microsoft.Practices.ServiceLocation.dll (and the corresponding XML file)
- Microsoft.Practices.Unity.dll (and the corresponding XML file)
The XML files provide Intellisense for the DLL files.
We copy the Prism DLLs to a solution library folder so that we can use a private, or side-by-side installation of Prism for our solution. This approach eliminates some of the versioning problems that can occur when Prism is updated. For example, I have several Prism 2.x solutions that I maintain. Since each solution uses its own Prism DLLs, the older version does not interfere with the newer version, and vice versa.
Step 1c – Set references to library DLLs: In the References section of Solution Explorer, add references to the DLLs added to the solution library in the preceding step.
Step 1d – Rename the Shell window: First, rename MainWindow.xaml to ShellWindow.xaml in Solution Explorer. Then, open Shell.xaml.cs in VS. The class will still be named MainWindow
. Rename the class using the Visual Studio refactoring tools; this will change all references to the class, as well.
Step 1e – Add a namespace definition to the Shell window: Add a XAML namespace definition to ShellWindow.xaml:
xmlns:prism=”http://www.codeplex.com/prism”
Step 1f – Lay out Shell window: ShellWindow.xaml will contain controls that are declared as regions, typically ContentControl
or ItemsControl
objects. Each of these controls represents a region in the Shell window. These regions will hold the content that is loaded into the Shell window.
ContentControl
regions are suitable for regions that will display one View at a time.ItemsControl
regions are suitable for regions that will display several Views at once, such as the task buttons in the screenshot above.
ShellWindow.xaml is laid out in much the same way as any other window, using grids, splitters, and other layout controls. The only difference is that the window contains few if any real controls. Instead, it contains placeholder controls that are declared as regions. Prism inserts content from regions into these placeholders later.
Each region control needs both a x:Name
and a RegionName
. The regular x:Name
is used by your code to refer to the control as needed. Prism’s RegionManager
uses the RegionName
to load content later. So, a region declaration based on an ItemsControl
looks like this:
<ItemsControl x:Name=”MyRegion” prism:RegionManager.RegionName=”MyRegion” />
Note that both names can be the same.
Step 1g – Create a Bootstrapper: The bootstrapper initializes a Prism app. Most of the heavy lifting is done by the Prism library, and the main task of the bootstrapper class created by the developer is to invoke this functionality. To set up the bootstrapper, first create a class in the Shell project called Bootstrapper.cs. Update the class signature to inherit from the UnityBootstrapper class.
A sample Bootstrapper
class is included in the download file. The sample class includes the method overrides discussed below.
Step 1h – Override the CreateShell() method: Create an override for the CreateShell()
method so that it looks like this:
protected override System.Windows.DependencyObject CreateShell()
{
/* The UnityBootstrapper base class will attach an instance
* of the RegionManager to the new ShellWindow. */
return new ShellWindow ();
}
This override creates a new ShellWindow
and sets it as the Prism Shell.
Step 1i – Override the InitializeShell() method: Create an override for the InitializeShell()
method of the UnityBootstrapper
class. The override method will set the base class Shell
property to the Shell window for the app. The override should look like this:
protected override void InitializeShell()
{
base.InitializeShell();
App.Current.MainWindow = (Window)this.Shell;
App.Current.MainWindow.Show();
}
Step 1j – Override the CreateModuleCatalog () method: This step assumes you want to use Module Discovery to populate the module catalog, where application modules are simply placed in a specified folder, where Prism will discover and load them. Create an override for the CreateModuleCatalog()
method of the UnityBootstrapper
class. The override method should look like this:
protected override IModuleCatalog CreateModuleCatalog()
{
var moduleCatalog = new DirectoryModuleCatalog();
moduleCatalog.ModulePath = @".\Modules";
return moduleCatalog;
}
All we need to do is specify the folder where modules will be placed when compiled. Here, we specify a Modules subfolder in the Shell project bin folder.
Step 1k – Override the ConfigureRegionAdapterMappings() method: If your application uses any Region Adapters, create an override for the ConfigureRegionAdapterMappings()
method of the UnityBootstrapper
class. Region Adapters are used to extend Prism region capabilities to controls (such as the WPF Ribbon) that cannot support Prism regions out-of-the-box. See Appendix E to the Developer's Guide to Microsoft Prism.
The override method should look like this:
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
// Call base method
var mappings = base.ConfigureRegionAdapterMappings();
if (mappings == null) return null;
// Add custom mappings
mappings.RegisterMapping(typeof(Ribbon),
ServiceLocator.Current.GetInstance<RibbonRegionAdapter>());
// Set return value
return mappings;
}
Step 1l – Modify the App class: The App
class represents the application—it is called when the application starts. We will use the App
class to set up our Prism application, by configuring our logging framework and calling the Bootstrapper
we created in the preceding steps. So, open App.xaml.cs, and add the following override method to the class:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// Configure Log4Net
XmlConfigurator.Configure();
// Configure Bootstrapper
var bootstrapper = new Bootstrapper();
bootstrapper.Run();
}
The logger configuration call assumes we are using the log4net framework, and that it is being configured in an App.config file. See the How-To document Configuring log4net for more information on configuring log4net in this manner.
Step 1m – Modify App.xaml: WPF applications are normally initialized by the StartupUri
attribute in App.xaml. This attribute simply points to the main window as the window to load on startup. Since we are taking control of initialization in the App
class, we don’t need the XAML attribute. So, open App.xaml and delete that attribute.
Step 1n – Create a custom logger class: Create a new custom logger class, implementing ILoggerFacade
, to enable Prism to log using your logging framework. A sample custom logger is attached to this document as Appendix B. It enables Prism logging with the log4net framework.
Step 1o – Initialize the custom logger: In the application bootstrapper, add an override to the CreateLogger()
method. The override simply needs to create and return an instance of your custom logger, like this:
protected override Microsoft.Practices.Prism.Logging.ILoggerFacade CreateLogger()
{
return new Log4NetLogger();
}
Step 2: Add Modules to the Application
Modules are logical units in Prism applications. Each module is set up as a separate Class Library (DLL) project within the Prism solution. A code listing for a sample module initializer class is attached to this document as Appendix B.
Step 2a – Create a module: Add a WPF User Control Library project to your solution. Name the module according to this pattern: [SolutionName] .Module[Module Name]. For example, if the solution is named MySolution, and the module represents a folder navigator, you might name it MySolution.ModuleFolderNavigator.
Step 2b – Add Prism references: Add references to the following Prism and Unity assemblies to your new module project:
- Microsoft.Practices.Prism.dll
- Microsoft.Practices.ServiceLocation.dll
- Microsoft.Practices.Unity.dll
- Microsoft.Practices.Prism.UnityExtensions.dll
The Microsoft.Practices.ServiceLocation reference is required for the Prism Service Locator, which is used to resolve objects from the DI container (see ConfigureRegionAdapterMappings()
in Appendix A to this article).
Step 2c – Rename the Module Initializer class: Rename the Class1.cs file in the new module project to [Module Name]. For example, if the module project is named MySolution.ModuleFolderNavigator, rename Class1
to ModuleFolderNavigator
.
Step 2d – Derive from the IModule interface: Change the class signature of the Module Initializer class to derive from the IModule
interface. VS will show error squiggles under the Interface name (because it is not yet implemented) and will offer to implement the interface. Tell VS to do so, and it will add the Initialize()
method required to implement IModule
. Leave the Initialize()
method as-is, for now.
Step 2e – Add folders to the module project: Add the following folders to the modules project to help organize the module code:
- Commands: Contains
ICommand
objects used by local View Models. - Services: Contains local services invoked by the commands.
- ViewModels: View Models for the module’s Views.
- Views: The module’s Views.
You may or may not use all of these folders, depending on how you structure your applications. For example, I use a Commands folder, because I prefer to encapsulate each ICommand
object in a separate class, rather than using Prism’s DelegateCommand
objects. I find that it separates code better and keeps my View Models from becoming cluttered.
Step 2f– Add a post-build event to each module: Since the whole point of Prism is to eliminate dependencies between the Shell and its modules, the VS compiler won’t be able to detect the app’s modules as dependencies of the Shell project, which means the modules won’t get copied to the Shell’s application output folder. As a result, each module will need a post-build-event command to copy the module to the Modules subfolder in the Shell project application output folder. That’s the folder Prism will search for application modules.
You can access the post-build command in the project's Properties pages, under the Build Events tab:
The command for the event should look like this:
xcopy /y "$(TargetPath)" "$(SolutionDir)<ShellProjectName>\$(OutDir)Modules\"
Where <ShellProjectName> = the solution name (e.g., “Prism4Demo.Shell”). Here is an example:
xcopy /y "$(TargetPath)" "$(SolutionDir)Prism4Demo.Shell\$(OutDir)Modules\"
Note also that if the module’s Views use any third-party or custom controls, the VS compiler won’t detect the third-party control as a dependency of the Shell, for the same reason it can’t detect the module. As a result, the module will need a separate post-build-event command to copy the third-party control to the Shell project application output folder. Here is an example:
xcopy /y "$(TargetDir)FsTaskButton.dll" "$(SolutionDir)Prism4Demo.Shell\$(OutDir)"
Note the use of the $(TargetDir) macro, followed by the name of the DLL to be copied to the Shell app output directory. The macro points VS to the output folder for the module’s assembly, which will contain the third-party control, since VS will detect the control as a dependency of the module.
Note also that we copy the third-party control to the root level of the output directory for the Shell assembly, not to its Modules sub-directory. If multiple modules use the same third-party control, we only need to include a post-build macro in one of those modules.
Finally, note the following with respect to the post-build command:
- The command needs to be contained in a single line. For example, if there is a line break between the source and destination paths, the command will exit with an error.
- There can be no spaces between macros and hard-coded portions of the command. For example, $(SolutionDir) Prism4Demo.Shell\$(OutDir)Modules\ will fail, because there is a space between (SolutionDir) and Prism4Demo.Shell\$(OutDir)Modules\.
- Macros contain their own terminating backslash, so you don’t need to add a backslash between a macro and a subfolder.
- Xcopy will create the Modules folder in the destination path, if it doesn’t already exist.
- The /y parameter suppresses the overwrite prompt for the Xcopy command. If this param is omitted, Windows will attempt to show the prompt after the first compile, which will cause Visual Studio to abort the command and exit with Code 2.
Step 3: Add Views to Modules
The next step in setting up a Prism application is to add Views to the application’s modules. The application UI is composed of module Views that Prism loads into the Shell window regions, which means that each View represents a part of the overall UI. Accordingly, Prism Views are generally contained in WPF UserControls.
Step 3a – Create the View: Create a View, which will usually take the form of a WPF User Control. You don’t need to do anything special to the View to enable its use with Prism. Note that the View may be a composite of several controls, as in a UI form, or it may consist of a single control, such as an Outlook-style Task Button or an application Ribbon Tab. In some cases, a View composed of a single control may derive from the control, rather than from UserControl
. For an example, see the Ribbon Tab Views in the demo app modules.
Step 3b – Register the View: In the module’s Initialize()
method, register the View with either the Prism Region Manager, or the Unity container. The choice of registry depends on the behavior desired from the View:
- If the View will be loaded when the module is loaded, and if it will last for the lifetime of the application, register the View with the Prism Region Manager. An example of this type of View would be an Outlook-style Task Button.
- If the View will be loaded and unloaded as the user navigates within the application, register the View with the Unity container—we will use
RequestNavigate()
later to load the View. Examples of this type of View would be a UI form, or an application Ribbon Tab.
The Initialize()
method in the module’s initializer class should look like this:
#region IModule Members
/// <summary>
/// Initializes the module.
/// </summary>
public void Initialize()
{
/* We register always-available controls with the Prism Region Manager, and on-demand
* controls with the DI container. On-demand controls will be loaded when we invoke
* IRegionManager.RequestNavigate() to load the controls. */
// Register task button with Prism Region
var regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
regionManager.RegisterViewWithRegion("TaskButtonRegion", typeof(ModuleATaskButton));
/* View objects have to be registered with Unity using the overload shown below. By
* default, Unity resolves view objects as type System.Object, which this overload
* maps to the correct view type. See "Developer's Guide to Microsoft Prism" (Ver 4),
* p. 120. */
// Register other view objects with DI Container (Unity)
var container = ServiceLocator.Current.GetInstance<IUnityContainer>();
container.RegisterType<Object, ModuleARibbonTab>("ModuleARibbonTab");
container.RegisterType<Object, ModuleANavigator>("ModuleANavigator");
container.RegisterType<Object, ModuleAWorkspace>("ModuleAWorkspace");
}
#endregion
Here are the method parameters for IRegionManager.RegisterViewWithRegion()
:
- Region name: The first parameter specifies the name of the region into which the View will be loaded.
- Type to register: The second parameter specifies the type of the View to be loaded into that region. Prism takes care of the actual loading of the View.
And here are the parameters for IUnityContainer.RegisterType<TFrom, TTo>()
:
- TFrom: The first type parameter should always be
System.Object
, because this is how Prism will initially resolve the View object. - TTo: The second type parameter should match the actual type of the View object. Unity will map the resolved object from
System.Object
to this type. - View name: The method parameter specifies the name of the View to be registered, as a string value.
By default, Prism creates a new instance of the View object every time a resolution is requested. You can override this behavior, and register a class as a singleton, by passing a second method parameter, in the form of a new ContainerControlledLifetimeManager()
.
Note that there are several other overloads for IUnityContainer.RegisterType()
, including an overload that allows the developer to specify an interface or base class to resolve, and the concrete type to return when a resolution is requested.
Step 3c – Implement the IRegionMemberLifetime interface: If the View should be unloaded when its host module is deactivated, implement the IRegionMemberLifetime
interface on either the View or its View Model. The interface consists of a single property, KeepAlive
. Setting this property to false
will cause the View to be unloaded when the user navigates to a different module. Here is sample code to implement the interface:
using Microsoft.Practices.Prism.Regions;
using Microsoft.Windows.Controls.Ribbon;
namespace Prism4Demo.ModuleA.Views
{
/// <summary>
/// Interaction logic for ModuleARibbonTab.xaml
/// </summary>
public partial class ModuleARibbonTab : RibbonTab, IRegionMemberLifetime
{
#region Constructor
public ModuleARibbonTab()
{
InitializeComponent();
}
#endregion
#region IRegionMemberLifetime Members
public bool KeepAlive
{
get { return false; }
}
#endregion
}
}
If KeepAlive
will always have a value of false
, then you can safely implement the interface on the View class and hard-code the value (as shown above). However, if the value of the property will be changed at run-time, implement the interface on the corresponding View Model instead, in order to avoid having the program’s back end interacting directly with the View.
Step 3d – Add the ViewSortHint Attribute: If the View will be loaded into an ItemsControl
region, or any other region that holds multiple items, you may want to add the ViewSortHint
attribute to the View. This attribute specifies the sort order of the View when it is loaded into the region. Here is an example for an Outlook-style Task Button:
[ViewSortHint("01")]
public partial class ModuleATaskButton : UserControl
{
public ModuleATaskButton()
{
InitializeComponent();
}
}
Note that the control declared as a Prism region must support sorting for the ViewSortHint
attribute to have any effect.
Step 4: Add a Common Project to the Application
A Prism production app will have numerous base classes, interfaces, CompositePresentationEvent
classes, and resource dictionaries. We can centralize dependencies and avoid duplication by placing all of these application resources in a single project. The modules contain a reference to this project, which enables them to invoke base and other classes directly, and to access resource dictionaries using ‘pack URLs’.
The Shell project can serve as the repository of these application-wide resources. However, that approach tightens coupling between the modules and the Shell, since modules can no longer be tested without the Shell. For that reason, I prefer to locate the resources in a class library designated as the Solution’s Common project.
I mentioned above that modules can use pack URLs to access resource dictionaries across assembly boundaries. A pack URL is simply a URL that contains a reference to another assembly on the user's machine, typically another project in the same solution. They are rather odd-looking, but they get the job done. I won't spend much time on them here, since they are well-documented on MSDN. Here is an example of a pack URL that references a resource dictionary called Styles.xaml in a Common project of a Prism app. The dictionary is being referenced at the application level of another project in the solution:
<Application.Resources>
<ResourceDictionary>
<!-- Resource Dictionaries -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="pack://application:,,,/Common;component/Dictionaries/Styles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Note that we don't use a resource dictionary in the demo app, so it doesn't contain any references of this sort.
I typically add the following folders to the Common project:
- BaseClasses
- Interfaces
- Events (for
CompositePresentationEvent
classes) - ResourceDictionaries
You can use a Common project as the repository for your business model and data access classes as well, but I prefer to put these classes in separate projects. I find that approach better for separating code and managing dependencies between layers of my application.
Conclusion
The preceding steps will produce the basic framework for a Prism application. There are a number of other elements involved in a production app, such as a View Model for each View defined in the application’s modules, and the back-end logic that implements the application’s use cases. You can find additional documentation for the demo app, as well as a discussion of loosely-coupled communication between modules, in the companion article cited above.
As always, I welcome peer review by other CodeProject users. Please flag any errors you may find and add any suggestions for this article in the Comments section at the end of the article. Thanks.
Appendix A: Sample Bootstrapper Class
The following listing shows the code for the Bootstrapper
class in the demo app:
using System.Windows;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.Prism.UnityExtensions;
using Microsoft.Practices.ServiceLocation;
using Microsoft.Windows.Controls.Ribbon;
using Prism4Demo.Shell.Views;
using PrismRibbonDemo;
namespace Prism4Demo.Shell
{
public class Bootstrapper : UnityBootstrapper
{
#region Method Overrides
/// <summary>
/// Populates the Module Catalog.
/// </summary>
/// <returns>A new Module Catalog.</returns>
/// <remarks>
/// This method uses the Module Discovery method
/// of populating the Module Catalog. It requires
/// a post-build event in each module to place
/// the module assembly in the module catalog
/// directory.
/// </remarks>
protected override IModuleCatalog CreateModuleCatalog()
{
var moduleCatalog = new DirectoryModuleCatalog();
moduleCatalog.ModulePath = @".\Modules";
return moduleCatalog;
}
/// <summary>
/// Configures the default region adapter
/// mappings to use in the application, in order
/// to adapt UI controls defined in XAML
/// to use a region and register it automatically.
/// </summary>
/// <returns>The RegionAdapterMappings instance
/// containing all the mappings.</returns>
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
// Call base method
var mappings = base.ConfigureRegionAdapterMappings();
if (mappings == null) return null;
// Add custom mappings
var ribbonRegionAdapter =
ServiceLocator.Current.GetInstance<RibbonRegionAdapter>();
mappings.RegisterMapping(typeof(Ribbon), ribbonRegionAdapter);
// Set return value
return mappings;
}
/// <summary>
/// Instantiates the Shell window.
/// </summary>
/// <returns>A new ShellWindow window.</returns>
protected override DependencyObject CreateShell()
{
/* This method sets the UnityBootstrapper.Shell property to the ShellWindow
* we declared elsewhere in this project.
* Note that the UnityBootstrapper base
* class will attach an instance
* of the RegionManager to the new Shell window. */
return new ShellWindow();
}
/// <summary>
/// Displays the Shell window to the user.
/// </summary>
protected override void InitializeShell()
{
base.InitializeShell();
App.Current.MainWindow = (Window)this.Shell;
App.Current.MainWindow.Show();
}
#endregion
}
}
Appendix B: Sample Module Class
The following listing shows the code for the ModuleA
class in the demo app:
using System;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.ServiceLocation;
using Microsoft.Practices.Unity;
using Prism4Demo.ModuleA.Views;
namespace Prism4Demo.ModuleA
{
/// <summary>
/// Initializer class for Module A of the Prism 4 Demo.
/// </summary>
public class ModuleA : IModule
{
#region IModule Members
/// <summary>
/// Initializes the module.
/// </summary>
public void Initialize()
{
/* We register always-available controls
* with the Prism Region Manager, and on-demand
* controls with the DI container.
* On-demand controls will be loaded when we invoke
* IRegionManager.RequestNavigate() to load the controls. */
// Register task button with Prism Region
var regionManager =
ServiceLocator.Current.GetInstance<IRegionManager>();
regionManager.RegisterViewWithRegion("TaskButtonRegion",
typeof(ModuleATaskButton));
/* View objects have to be registered
* with Unity using the overload shown below. By
* default, Unity resolves view objects as
* type System.Object, which this overload
* maps to the correct view type. See "Developer's
* Guide to Microsoft Prism" (Ver 4), p. 120. */
// Register other view objects with DI Container (Unity)
var container = ServiceLocator.Current.GetInstance<IUnityContainer>();
container.RegisterType<Object, ModuleARibbonTab>("ModuleARibbonTab");
container.RegisterType<Object, ModuleANavigator>("ModuleANavigator");
container.RegisterType<Object, ModuleAWorkspace>("ModuleAWorkspace");
}
#endregion
}
}
Appendix C: Sample Custom Logger Class
The following listing shows the code for a sample custom logger:
using log4net;
using Microsoft.Practices.Prism.Logging;
namespace FsNoteMaster3
{
class Log4NetLogger : ILoggerFacade
{
#region Fields
/* Note that the ILog interface and
* the LogManager object are Log4Net members.
* They are used to instantiate the Log4Net
* instance to which we will log. */
// Member variables
private readonly ILog m_Logger =
LogManager.GetLogger(typeof(Log4NetLogger));
#endregion
#region ILoggerFacade Members
/// <summary>
/// Writes a log message.
/// </summary>
/// <param name="message">The message to write.</param>
/// <param name="category">The message category.</param>
/// <param name="priority">Not used by Log4Net; pass Priority.None.</param>
public void Log(string message, Category category, Priority priority)
{
switch (category)
{
case Category.Debug:
m_Logger.Debug(message);
break;
case Category.Warn:
m_Logger.Warn(message);
break;
case Category.Exception:
m_Logger.Error(message);
break;
case Category.Info:
m_Logger.Info(message);
break;
}
}
#endregion
}
}
发表评论
Your style is really unique in comparison to other people I ave read stuff from. Thanks for posting when you have the opportunity, Guess I all just book mark this blog.
I think you have noted some very interesting details , appreciate it for the post.
Say, you got a nice article.Really thank you! Really Great.
very good publish, i certainly love this website, carry on it
I value the article.Much thanks again. Want more.
Really appreciate you sharing this blog post.Thanks Again. Really Great.
Major thankies for the article post.Really looking forward to read more. Want more.
Wow! This could be one particular of the most useful blogs We have ever arrive across on this subject. Basically Excellent. I am also an expert in this topic therefore I can understand your hard work.
Very good post. I absolutely appreciate this site. Keep it up!
I truly appreciate this blog.Really thank you! Fantastic.
Wow! This blog looks just like my old one! It as on a entirely different subject but it has pretty much the same page layout and design. Wonderful choice of colors!
Lovely good %anchor%, We have currently put a different one down on my Xmas list.
That is a beautiful photo with very good light
Thanks for sharing, this is a fantastic blog. Awesome.
I think other web-site proprietors should take this web site as an model, very clean and fantastic user friendly style and design, as well as the content. You are an expert in this topic!
I really liked your article post.Thanks Again. Really Great.
overlapping. I just wanted to give you a quick heads up! Other then that,
This site was how do I say it? Relevant!! Finally I have found something which helped me. Cheers!
This is very interesting, You are a very skilled blogger. I have joined your rss feed and look forward to seeking more of your fantastic post. Also, I ave shared your web site in my social networks!
Wow, superb blog layout! How long have you been blogging for? you made blogging look easy. The overall look of your site is wonderful, let alone the content!
Say, you got a nice blog article.Thanks Again. Fantastic.
of writing? I have a presentation subsequent week,
I think this is a real great post. Really Great.
Normally I do not learn article on blogs, however I wish to say that this write-up very pressured me to try and do it! Your writing style has been amazed me. Thanks, quite nice article.
Very neat post.Much thanks again. Want more.
You made several nice points there. I did a search on the issue and found most people will go along with with your blog.
Some really prime posts on this internet site , saved to favorites.
jas6w0 The electronic cigarette makes use of a battery and a small heating aspect the vaporize the e-liquid. This vapor can then be inhaled and exhaled
You made some really good points there. I checked on the internet for additional information about the issue and found most individuals will go along with your views on this site.
well written article. I all be sure to bookmark it and come back to read more
This is a topic that as close to my heart Cheers! Exactly where are your contact details though?
You have remarked very interesting points ! ps decent website.
Normally I do not learn post on blogs, however I wish to say that this write-up very pressured me to take a look at and do so! Your writing style has been amazed me. Thank you, quite nice post.
Major thanks for the post.Really thank you! Really Great.
This is one awesome post.Thanks Again. Keep writing.
the back! That was cool Once, striper were hard to find. They spend
Really appreciate you sharing this blog post.Much thanks again. Really Cool.
Informative and precise Its hard to find informative and accurate info but here I noted
It as hard to find knowledgeable people about this topic, but you sound like you know what you are talking about! Thanks
There are certainly a number of particulars like that to take into consideration. That is a great point to bring up.
This brief posting can guidance you way in oral treatment.
What i don at realize is in truth how you are not really a lot more well-favored than you
Wow, incredible weblog structure! How long have you been running a blog for? you made running a blog look easy. The overall look of your web site is wonderful, let alone the content material!
Wow! In the end I got a weblog from where I be able
pretty beneficial material, overall I think this is worthy of a bookmark, thanks
I truly enjoy examining on this site, it has fantastic articles.
You have made some good points there. I looked on the net to find out more about the issue and found most people will go along with your views on this website.
I value the post.Thanks Again. Really Cool.
Thank you for your article post.Really thank you! Great.
wow, awesome blog post.Really looking forward to read more. Want more.