Introduction
Xenta is an extensible n-tier application with multilayered architecture. Xenta is an open source project and available for free. It is developed in .NET/C# programming language and initially targeted to web based solutions. It's licensed under the modified MIT license.
The purpose of this article is to introduce you to the architecture of Xenta.
To work with the source code, you will need Microsoft Visual Studio 2010.
Architecture Overview
Xenta has 4 layers:
- Data Access Layer (DAL)
- Business Logic Layer (BLL)
- Service Layer
- Presentation Layer
Each layer contains its own entities. The entities are mapping while going through layers (e.g. DAL entities are mapping into BLL entities).
Data Access Layer
DAL consists of data abstraction and implementations of abstraction for each concrete data source. Data abstraction describes data entities and data provider interfaces. Base class for data entities is DataEntityBase
. For data provider interfaces, there is base interface IDataProvider
. Providers instantiation is based on configuration file. By default, the data source is Microsoft SQL Server (2005 or above), but believe me, it is easy enough to change the data source.
IDataProvider - Base Class for Data Provider Interfaces
public interface IDataProvider
{
#region Methods
void Initialize(string name, NameValueCollection properties);
#endregion
}
IUserDataProvider - Interface of User Data Provider
public interface IUserDataProvider : IDataProvider
{
#region Methods
#region C
bool InsertUser(Guid guid, string username, string firstName,
string lastName, Gender gender, DateTime? birthDate, string email,
string comment, string passwordHash, string passwordSalt,
int languageID, int currencyID, int timeZoneID, int countryID,
bool isActive, DateTime createdOn, DateTime updatedOn, out int userID);
#endregion
#region R
UserData GetUser(int userID);
UserData GetUserByEmail(string email);
UserData GetUserByUsername(string username);
UserData GetUserByGuid(Guid guid);
UserDataCollection GetAllUsers(string searchTerm, int? roleID,
int? countryID, int? languageID, int? currencyID, int? timeZoneID,
DateTime? createdOnStart, DateTime? createdOnEnd, bool showHidden,
int startIndex, int count, out int totalCount);
#endregion
#region U
bool UpdateUser(int userID, Guid guid, string username, string firstName,
string lastName, Gender gender, DateTime? birthDate, string email,
string comment, string passwordHash, string passwordSalt, int languageID,
int currencyID, int timeZoneID, int countryID, bool isActive,
DateTime createdOn, DateTime updatedOn);
#endregion
#region D
bool DeleteUser(int userID);
#endregion
#endregion
}
DAL Configuration Sample
<xentaDAL>
<providers>
...
<add name="UserDataProvider" type="SiberTek.Xenta.Data.Providers.UserDataProvider,
SiberTek.Xenta.Data.MsSqlServer.Core" connection="MsSqlServerConnection" />
...
</providers>
</xentaDAL>
Business Logic Layer
BLL describes business rules and management logic. It contains business entities, managers, tasks, etc..
Entities inherit from BusinessEntityBase
.
Each manager implements IManager
interface. Usually manager is singleton. Manager can contain events, which can be handled by other managers or tasks or something else. For example, there defined EntityEventArgs
class and EntityEventHandler
delegate, for events related to entity operations such as Create
/Update
/Delete
.
UserManager - User Manager Class Sample
public class UserManager : IManager
{
#region Constants
private const string GuestUsername = "guest";
#endregion
#region Fields
private static UserManager _instance = null;
private bool _initialized;
private IUserDataProvider _userDataProvider;
private IUserAttributeDataProvider _attributeDataProvider;
private IUserSessionDataProvider _sessionDataProvider;
#endregion
#region Constructors
private UserManager()
{
_initialized = false;
_userDataProvider = null;
_attributeDataProvider = null;
_sessionDataProvider = null;
}
#endregion
#region Events
public event EntityEventHandler UserCreated;
public event EntityEventHandler UserUpdated;
public event EntityEventHandler UserDeleted;
#endregion
#region Properties
public static UserManager Instance
{
get
{
if(_instance == null)
{
_instance = new UserManager();
}
return _instance;
}
}
...
Tasks implement ITask
interface. Each task has Period
property, this property contains a TimeSpan
value, which defines task execution period.
Tasks are scheduled by the TaskScheduler
class. This class starts a thread which reads execution schedule form a text file each one second, runs required tasks and reschedules next execution time for each task in list.
ITask - Task Interface
public interface ITask
{
#region Properties
TimeSpan Period
{
get;
}
#endregion
#region Methods
void Initialize(NameValueCollection properties);
void TaskProc();
#endregion
}
Tasks Configuration Sample
<tasks>
...
<add name="MessageDispatcher" type="SiberTek.Xenta.Tasks.MessageDispatcher,
SiberTek.Xenta.Core" />
...
</tasks>
Service Layer
This "thin" layer is based on WCF technology. It allows to deploy Xenta tiers on different physical servers, in one server or even in one application. Services are hosted in Xenta.Services.Host
application.
ICoreService - Core Service Contract
[ServiceContract]
public interface ICoreService
{
#region Methods
#region Users
[OperationContract]
[FaultContract(typeof(XentaFault))]
User CreateUser(string username, string firstName, string lastName,
Gender gender, DateTime? birthDate, string email, string comment,
string password, int languageID, int currencyID, int timeZoneID, int countryID);
[OperationContract]
User GetUser(int userID);
[OperationContract]
User GetGuestUser();
[OperationContract]
User GetUserByUsername(string username);
[OperationContract]
User GetUserByEmail(string email);
[OperationContract]
User GetUserByGuid(Guid guid);
[OperationContract]
UserCollection GetAllUsers(string searchTerm, int? roleID, int?
countryID, int? languageID, int? currencyID, int? timeZoneID, DateTime?
createdOnStart, DateTime? createdOnEnd, bool showHidden, int startIndex, int count);
[OperationContract]
bool IsUsernameBusy(string username);
[OperationContract]
bool IsEmailBusy(string email);
[OperationContract]
bool IsUserPasswordValid(int userID, string password);
[OperationContract]
[FaultContract(typeof(XentaFault))]
bool UpdateUserPassword(int userID, string password);
[OperationContract]
[FaultContract(typeof(XentaFault))]
bool ActivateUser(int userID);
[OperationContract]
bool IsUserActive(int userID);
...
Presentation Layer
The layer implements MVP pattern. UI is based on ASP.NET technology and split into several web applications such as: Xenta.Web.Admin
, Xenta.Web.Home
, Xenta.Web.FileStorage
, Xenta.Web.Forum
. You decide which application to deploy.
In context of MVP pattern there:
- Model - Business logic which is accessible through services
- View - Web applications
- Presenter - Presenters and helper classes which access model through services
UserPresenter - User Presenter Class Sample
public class UserPresenter : PresenterBase
{
#region Methods
public void Update()
{
ViewDataContainer item = View.DataContainer["User"] as ViewDataContainer;
int userID = (int)item["UserID"];
string username = (string)item["Username"];
string email = (string)item["Email"];
string firstName = (string)item["FirstName"];
string lastName = (string)item["LastName"];
bool isActive = (bool)item["IsActive"];
Gender gender = (Gender)item["Gender"];
DateTime? birthDate = (DateTime?)item["BirthDate"];
string comment = (string)item["Comment"];
int languageID = (int)item["LanguageIDv];
int countryID = (int)item["CountryID"];
int timeZoneID = (int)item["TimeZoneID"];
int currencyID = (int)item["CurrencyID"];
if(birthDate.HasValue)
{
birthDate = UserContext.Current.DateTimeConvertToUtc(birthDate.Value);
}
using(CoreServiceClient svc = new CoreServiceClient())
{
svc.UpdateUser(userID, username, firstName, lastName,
gender, birthDate, email, comment, languageID, currencyID,
timeZoneID, countryID, isActive);
}
}
...
UserForm - ASP.NET Control
public partial class UserForm : ViewBase<UserPresenter>, IForm, ICommandHandler
{
#region Properties
[Browsable(false)]
public int? UserID
{
get
{
return StringHelper.Parse<Int32?>(txtUserID.Text);
}
set
{
txtUserID.Text = value.HasValue ? value.ToString() : String.Empty;
}
}
public string ValidationGroup
{
set
{
vldUsername.ValidationGroup = value;
vldEmail.ValidationGroup = value;
vldEmail2.ValidationGroup = value;
vldPassword.ValidationGroup = value;
vldPassword2.ValidationGroup = value;
vldFileQuota.ValidationGroup = value;
}
}
...
Conclusion
Xenta has a clear architecture and implements best patterns & practices. It's easy to extend. It gives you freedom in packaging and deploying. You can just throw out all unnecessary things and build your own package. We welcome you in our community, where you can receive useful answers to any of your questions.
References