Introduction
In this paper, an example of infrastructure will be presented to projects such as erp, mrp, and crm. Considering the challenge and variety of user requests in such projects, it is inevitable that a dynamic and straightforward back-end and front-end work suitable for software standards has been performed. Given the complexity of the job too, it is appropriate that such a work environment has been created. Senior software developers set the software standards and junior software developers also performs end user processes from which they obtain the analysts by using these standards. When intending to software standards, it includes the creation of database model, implementation of n-layered architecture and application of various display examples. After senior software developers set these standards, it remains only one thing to be done: To copy and paste these screens and code blocks; to change label, database links and relevant page information and to present the screen to the end user. Therefore, it is crucially important to serialize the standard set. Let’s suppose that hundreds of screen were created by serializing under certain standards through such a work and then senior software developers wanted to make a change at a standard they set. Senior software developer must capture code block of this standard prepared for all screens with the aid of regular expression (of course, this must only be relevant code block just so) and should be replaced with new one.
Background
Firstly, let us look at what kind of actions should be at the presentation layer. Divide into four parts with the help of screen splitter. Be a navigation menu created according to user authorization at the left, user activities with a project’s logo and name and login at the upper, a footer area at the bottom and a content area where our page pops up at the middle. Design left navigation menu in a close way to database build. Thus, be compatible database design, navigation menu, grid which will be filled in the content area, tree image and roller tree image. For example, Template Process > Master Template in the navigation menu shows a grid. When clicking the grid detail, Detail Template 1 and Detail Template 2 grids are seen. When clicking Detail Template 1 in the navigation menu, open a full list that contains all records of Detail Template 1.
If a screen to be opened in the content page consists of a grid, features of this grid should be as follows:
DevExpress controls offers filtering, sorting, adding and deleting column(s), replacing the column, writing custom queries, exporting, etc. to us. But most importantly, it is being required to set all of these features from which they were called. For example, when opening the detail of Master Template Grid on the following screen, I should be able to modify desired properties of the Detail Template 1 grid. When clicked the detail tabs, I would like to open relevant tab at a certain width and height and to hide the columns in the master table after the detail pops up.
Grid column may vary according to the related data type. For example, that is the template for int columns.
Settings.Columns.Add(column =>
{
column.FieldName = "IntColumn";
column.Caption = Translator.TemplateIntColumn;
column.ColumnType = MVCxGridViewColumnType.SpinEdit;
((SpinEditProperties)column.PropertiesEdit).NumberFormat = SpinEditNumberFormat.Number;
((SpinEditProperties)column.PropertiesEdit).NumberType = SpinEditNumberType.Integer;
});
Be an edited screen as follows:
Able to resize edit forms and give percentage heights to certain objects in the relevant tab while resizing. For example, it is said that heights of Multiline column and HTML column increase with certain percentage when height on the screen is increased by resizing.
Through a menu next to Comboboxes, able to add a new record, change or delete the selected record or go to the page combobox related to the show grid. Opened pages with the aid of the menu Combobox are shown on a popup menu, allowing unlimited pages to open in a nested manner. For example, a screen below will pop up when we edit the ComboBox person on the screen:
Able to click the button Add if any Father record is not for the record Fatih Doğanay on this re-opened window. When clicked the Add, open “Add New Person” page again and enter a new record. After adding the new record, an illustration makes on the Father combobox. While end user works on the screens with such a work, he/she does not need to comply with the order of data entry. In other words, he/she does not need to firstly enter this record so as to get selected Father record.
How do such an unlimited opening and not confusing id and names of similar objects on the screen achieve? Each edit form contains a code that this popup will use when a new screen will open. Not-confusing their id and names resembles exactly like namespace logic in the .net platform and all objects are taking their names by deriving from these namespaces.
@Html.Action("ActionPartial", "Popup", new ActionPopupModel
{
Name = Model.Name + "Edit"
})
For example, name of Father combobox found in the uppermost on the screen above is cmbFatherAccountPersonGrid1Edit. Namely, this means that No. 1 id of AccountPerson grid was edited and the screen adding was opened for this, too.
Address operations are running compatible with Google Maps. When end user starts to enter data, records such as country, province, district, address do not need to be present in the system.
When clicked Google Maps, the screen below is opened.
If it clicks on Save to address, records of available country, province, district and address add to database or is updated, if any. It is indicated what happens when clicked on Save to address in the map menu on which Google Maps calls. It is requested that these values in the example be entered into the relevant controls (combobox, spinedit, etc).
Example columns of FilePath and ImagePath are given for file and image processes. When clicked on the button ... next to edit control, the following screen is popped up.
Allowed file extensions, max upload size, location to be recorded and values of min-width, max-width, min-height and max-height can be entered as parameter.
GridLookup is used for processes to be made multiple choice.
There is AccountPermission table for permissions and their records continue as follows. Read TemplateMaster, Insert TemplateMaster, Update TemplateMaster, Delete TemplateMaster, Export TemplateMaster, Read TemplateDetail1, Insert TemplateDetail1 ...
There is AccountRole table in the database for role operations. Existing records are Full authorize, General manager, Editor. There is AccountRolePermission table for role permissions.
Each user has a role and there is AccountUserPermission table of user-specific authorizations. Extra roles can be added to or removed from user’s role authorizations. Adding role is normal but how should role be removed? This process operates according to xor logic.
AccountRolePermission
| AccountUserPermission
| Authorization Result
|
No record
| No record
| Not allowed
|
No record
| Record available
| Extra permissions available.
|
Record available
| No record
| Permissions available from role.
|
Record available
| Record available
| Not allowed. Role overridden
|
Roles for full list on the role permissions screen must be located in the root part of tree.
Users for full list on the user permissions screen must be located in the root part of tree. When opened the details, roots should be dropped.
Authorities can be managed the screens. For example, if user does not have the authority Insert for Detail 1, the button Insert on the grid menu needs to be removed. For example, if user does not have the monitoring authority Insert for Detail 1, it is required that relevant link be removed from the navigation menu and that warning message “You are not authorized for Detail template 1” be prompted in the locations that have access to this page. Adding this message to this prevention and related ViewContext is possible through PermissionAuthorize derived from AuthorizeAttribute.
[PermissionAuthorize("TemplateDetail1", Permission.ReadTemplateDetail1)]
public ActionResult GridInit(TemplateDetail1GridModel model)
Permission shown in Permission.ReadTemplateDetail1 should be an enum. Enum is derived from data in the table AccountPermission found in the database with script and its values correspond to Id’s.
public enum Permission : int
{
None = 0,
ReadTemplateMaster = 1,
InsertTemplateMaster = 2,
UpdateTemplateMaster = 3
...
Microsoft Membership Provider is used in this project. User roles are retrieving as xor through CustomRoleProvider > GetRolesForUser, as mentioned before. With IsUserInRole, it is checking whether user has a role. Microsoft Membership has used “Rol” as name here but this has been used as permission in our system.
How should database be managed?
Let all tables in the database, even including tables with n:n relationships, necessarily include Id, InsertDate, InsertedBy, UpdateDate, UpdatedBy and IsDeleted columns. Be a RowNumber area in the tables that we need to change the record sorting, especially as is in the type tables. It is not mandatory but these RowNumber fields can have cluster index in terms of speed.
Unique fields should be managed as follows: For example, RoleName should be unique in the role table. Supposing that record “General manager” is deleted from this table and then record “General manager” is added to. Here, the restore process should step into.
Likewise, let us warn when trying an existing record to add again.
Naming must be based on a certain logic so that we can be processed them in a practical way by capturing unique operations from our database, just as IX_AccountRole_RoleName_Unique.
There is language support for captions, messages, etc. on the MVC side. But we may want data in the database side to have language support. If, for example, we want fields of RoleName and RoleDescription in the table AccountRole to have language support, we must create AccountRoleLanguage table. Just as in the operation of the resource file in the .net side, we can say “Get” if there exists any record in the AccountRoleLanguage table or load the default value in the AccountRole. Indeed, Role language tab on the screen above corresponds to the table of AccountRoleLanguage.
Now I am adding a new record to AccountRole. I get the following warning message when I click on the tab Role name: Secretary, Description: null. Role language.
When I click on the button OK, I record data and reopen it in the edited form and can now enter necessary record for Role Language.
Implement N-Tier Architecture
Core layer contains objects covering the whole project and data access objects for DalSqlServer Sql Server and Service layer contains objects that process list of operations. Our study is designed as database first. But necessary codes are added to T4 template to implement model we want. In order to apply dependency injection, VillageSocietyModel.tt that Entity Framework is created under DalSqlServer > VillageSocietyModel.edmx as default extracts from here, moving to Core layer. Therefore, EntityFramework enables the entities to create in the Core layer.
Now let’s see what sort of infrastructure is created for AccountPerson as an example.
At the layer DalSqlServer
public partial class VillageSocietyEntities : DbContext
{
public virtual DbSet<Core.AccountPerson> AccountPerson { get; set; }
...
At the layer Core
public partial class AccountPerson : BaseEntity
{
public int VillageId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsMale { get; set; }
public string PicturePath { get; set; }
public int? Father { get; set; }
public int? Mother { get; set; }
...
public class BaseEntity
{
public int Id { get; set; }
public DateTime InsertDate { get; set; }
public int InsertedBy { get; set; }
public DateTime? UpdateDate { get; set; }
public int? UpdatedBy { get; set; }
public DateTime? DeleteDate { get; set; }
public int? DeletedBy { get; set; }
public bool IsDeleted { get; set; }
}
public interface IAccountPersonService : IBaseService<AccountPerson>
{
IQueryable<AccountPersonEx> GetEx(Expression<Func<AccountPerson, bool>> filter = null,
Expression<Func<SystemVillage, bool>> filterVillage = null,
Expression<Func<AccountPerson, bool>> filterFather = null,
Expression<Func<AccountPerson, bool>> filterMother = null);
}
public partial class AccountPersonEx : AccountPerson
{
public string VillageName { get; set; }
public string FatherFirstName { get; set; }
public string FatherLastName { get; set; }
public string MotherFirstName { get; set; }
public string MotherLastName { get; set; }
}
public interface IBaseService<TEntity> where TEntity : BaseEntity
{
IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null);
TEntity First(Expression<Func<TEntity, bool>> filter);
TEntity FirstOrDefault(Expression<Func<TEntity, bool>> filter);
TEntity Single(Expression<Func<TEntity, bool>> filter);
TEntity SingleOrDefault(Expression<Func<TEntity, bool>> filter);
TEntity GetById(int id);
void Insert(TEntity item);
void Update(TEntity item);
void Edit(TEntity item, params string[] ignoreButSomeProperties);
void Delete(TEntity item);
void Restore(TEntity item, bool ignoreProperties);
void Save();
UniqueKeyException ParseUniqueKeyException(Exception exception, TEntity item);
JsonResultModel ExceptionToJsonResult(Exception exception, TEntity item, bool isNewRecord);
}
At the layer Service
public class AccountPersonService : BaseService<AccountPerson>, IAccountPersonService
{
public AccountPersonService()
: base(IoC.Resolve<IUnitOfWork>())
{
}
public AccountPersonService(IUnitOfWork uow)
: base(uow)
{
}
public IQueryable<AccountPersonEx> GetEx(Expression<Func<AccountPerson, bool>> filter = null,
Expression<Func<SystemVillage, bool>> filterVillage = null,
Expression<Func<AccountPerson, bool>> filterFather = null,
Expression<Func<AccountPerson, bool>> filterMother = null)
{
filter = filter ?? (p => true);
filterVillage = filterVillage ?? (p => true);
filterFather = filterFather ?? (p => true);
filterMother = filterMother ?? (p => true);
var items = repository.Get();
var villages = uow.Repository<SystemVillage>().Get();
var persons = uow.Repository<AccountPerson>().Get();
var query = from item in items.Where(filter)
join village in villages.Where(filterVillage) on item.VillageId equals village.Id
from father in persons.Where(p => p.Id == item.Father).DefaultIfEmpty().Where(filterFather)
from mother in persons.Where(p => p.Id == item.Mother).DefaultIfEmpty().Where(filterMother)
select new AccountPersonEx
{
Id = item.Id,
VillageId = item.VillageId,
FirstName = item.FirstName,
LastName = item.LastName,
IsMale = item.IsMale,
PicturePath = item.PicturePath,
Father = item.Father,
Mother = item.Mother,
InsertDate = item.InsertDate,
InsertedBy = item.InsertedBy,
UpdateDate = item.UpdateDate,
UpdatedBy = item.UpdatedBy,
DeleteDate = item.DeleteDate,
DeletedBy = item.DeletedBy,
IsDeleted = item.IsDeleted,
VillageName = village.VillageName,
FatherFirstName = father.FirstName,
FatherLastName = father.LastName,
MotherFirstName = mother.FirstName,
MotherLastName = mother.LastName
};
return query;
}
}
public class BaseService<TEntity> : IBaseService<TEntity>, IDisposable where TEntity : BaseEntity
{
internal IUnitOfWork uow;
internal readonly IRepository<TEntity> repository;
public BaseService(IUnitOfWork uow)
{
this.uow = uow;
this.repository = uow.Repository<TEntity>();
}
public virtual IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null)
{
return repository.Get(filter);
}
public virtual TEntity First(Expression<Func<TEntity, bool>> filter)
{
return repository.First(filter);
}
public virtual TEntity FirstOrDefault(Expression<Func<TEntity, bool>> filter)
{
return repository.FirstOrDefault(filter);
}
public virtual TEntity Single(Expression<Func<TEntity, bool>> filter)
{
return repository.Single(filter);
}
public virtual TEntity SingleOrDefault(Expression<Func<TEntity, bool>> filter)
{
return repository.SingleOrDefault(filter);
}
public virtual TEntity GetById(int id)
{
return repository.GetById(id);
}
public virtual void Insert(TEntity item)
{
repository.Insert(item);
}
public virtual void Update(TEntity item)
{
repository.Update(item);
}
public virtual void Edit(TEntity item, params string[] ignoreButSomeProperties)
{
repository.Edit(item, ignoreButSomeProperties);
}
public virtual void Delete(TEntity item)
{
repository.Delete(item);
}
public virtual void Restore(TEntity item, bool ignoreProperties)
{
repository.Restore(item, ignoreProperties);
}
public void Save()
{
uow.Save();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
uow.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public UniqueKeyException ParseUniqueKeyException(Exception exception, TEntity item)
{
return repository.ParseUniqueKeyException(exception, item);
}
public JsonResultModel ExceptionToJsonResult(Exception exception, TEntity item, bool isNewRecord)
{
return repository.ExceptionToJsonResult(exception, item, isNewRecord);
}
}
In fact, all of db operations perform through repositories. There is a UnitofWork that uses all repositories and UnitOfWork also moves to Service layer as a parameter. In such a use, the same dbcontext has used for a list of work and transactions can be performed in the Service layer. For example, there is an InsertOrUpdate method in the AddressService class.
public AddressSetStruct InsertOrUpdate(AddressCountry country, AddressCity city, AddressDistrict district, Address address)
{
var countryRepository = uow.Repository<AddressCountry>();
var countryDb = countryRepository.Get(p => p.CountryCode == country.CountryCode).SingleOrDefault();
var cityRepository = uow.Repository<AddressCity>();
var cityDb = cityRepository.Get(p => p.CityName == city.CityName).SingleOrDefault();
var districtRepository = uow.Repository<AddressDistrict>();
var districtDb = districtRepository.Get(p => p.DistrictName == district.DistrictName).SingleOrDefault();
var addressRepository = uow.Repository<Address>();
var addressDb = addressRepository.Get(p => p.FullAddress == address.FullAddress).SingleOrDefault();
...
In this method, values of country, city, district and address in the GoogleMaps go as parameter and if transaction is successful with context.save, relevant records are updated or new records are created.
As in the AccountPerson, all classes must not be derived from BaseEntity. Field of RowNumber is available in the AccountRole table and we want to array the records. In such a case, related codes for AccountRole it should be built as follows:
For it has T4 template and AccountRole RowNumber field, it should be derived from RowNumberedEntity.
public partial class AccountRole : RowNumberedEntity
{
public string RoleName { get; set; }
public string RoleDescription { get; set; }
...
public class RowNumberedEntity : BaseEntity
{
public double RowNumber { get; set; }
}
public interface IAccountRoleService : IRowNumberedService<AccountRole>
{
IQueryable<AccountRoleEx> GetWithLanguage(Expression<Func<AccountRole, bool>> filter, int languageId);
}
public class AccountRoleService : RowNumberedService<AccountRole>, IAccountRoleService
{
public AccountRoleService()
: base(IoC.Resolve<IUnitOfWork>())
{
}
public AccountRoleService(IUnitOfWork uow)
: base(uow)
{
}
...
public class RowNumberedService<TEntity> : BaseService<TEntity>, IRowNumberedService<TEntity>, IDisposable where TEntity : RowNumberedEntity
{
public RowNumberedService(IUnitOfWork uow)
: base(uow)
{
}
public bool Up(int id1, int? id2)
{
...
}
public bool Down(int id1, int? id2)
{
...
}
}
When a record selects, the button up moves this record to one-up. When clicked on the button up after selecting two records, the record below will move onto the record above. This situation is true for the button Down.
In the future...
- Current working system is, in particular, available for grid and treelists and its layouts (column order, column, visible columns, filters, etc) write to SystemLayout table in the database and read from there. Therefore, if an end user leaves a screen in what way, then it is guaranteed that he/she resumes. However, end user should be able to create many cases and able to select the case he/she wants to see.
- Html editor can be used as a reporting tool. For example, a template is designed for Person grid as follows. When a command “Create the report” is issued after selecting the records required, templates of relevant records are generated serially. Thus, it allows an end user to get a designed output through the records. Created templates and PDF outputs can be saved to the database.
- End user can be saved an Sql outcome – that someone sees to work – to the system and can view these records on a grid dynamically. Someone can decide where link on the navigation menu will be found and connect to the relevant roles and users by means of this authorization. Therefore, the reporting tool mentioned in the second item would be made more effective with this item.
Hoping to convey your opinions and suggestions
http://bestprogramming@hotmail.com