X
dnn.blog.
Back

Using DAL 2 in a real world module

While converting the data access layer for the DNN FAQ module, I came across some pitfalls. There is some documentation on the internet (*), but this remains more on the surface. And moreover, the datalayer is still missing an important part in the implementation (more on that later). So this document should help you people not making the same mistakes as I did when going my first steps with DAL 2.

Basics on DAL 2

The main target of the new data layer is to free the developer of the massive burdon to write these ever repeating tasks while developing the data access layer. Up til now we had to write a method in XYZController, an abstract method in DataProvider and a concrete method in SqlDataprovider. In most cases you have to write an additional stored procedure including installation and deinstallation script.

Another goal is the opportunity to work in a more modern way with typed data objects (POCOs) and with IEnumerables of them instead of untyped datareaders and datatables. To realize this and some other features (e.g. automatic caching), DNN integrated the PetaPoco Class Library since version 7 in the core.

The DataInfo objects /null values

My first step when converting the FAQs module to DAL 2 leaded me to the two datainfo classes for the faqs (FAQInfo.cs) and the categories (CategoryInfo.cs). Initially the classes implemented the IHydratable and also (for token replace) the IPropertyAccess interface. The code for IHydratable is now obsolete; hydrating is part of PetaPoco. The IPropertyAccess interface remains unchanged because I want to work with token replace also in the future. For better clarity and readability I change all the properties to automatic properties ({get;set;}). As a last step I add data annotations needed by PetaPoco (e.g. [PrimaryKey…], see other documentations for these topics). I end up with 183 lines of code instead of 443 at the beginning (a lot of comment lines are stripped out here):

using DotNetNuke.ComponentModel.DataAnnotations;
using System.Web.Caching;

namespace DotNetNuke.Modules.FAQs
{
	[TableName("FAQsCategory")]
	[PrimaryKey("FaqCategoryId",AutoIncrement = true)]
	[Scope("ModuleId")]
	[Cacheable("FAQsCategory",CacheItemPriority.Normal,20)]
	public class CategoryInfo
	{
	 	public int? FaqCategoryParentId { get; set; }
		public int FaqCategoryId { get; set; }
		public int ModuleId { get; set; }
		public string FaqCategoryName { get; set; }
		public string FaqCategoryDescription { get; set; }
		[IgnoreColumn]
		public int Level { get; set; }
		public int ViewOrder { get; set; }
	}
}

As an example I list here the CategoryInfo class. For your information:  The categories are ordered hierarchical for display in a treeview (so we have an ID and a ParentId).

Two things are notable:

  1. By using PetaPoco, the handling of null values is much easier . Fields which could hold null values in the database can by defined as nullable datatypes in the info class  (e.g. int?, datetime?, string). Assigning a null value to such a property and persisting this with PetaPoco leads directly to a null value in the database and vice versa. No more need to conversion of values with Null.GetNull oder usage of if (xy == DBNull.value)  is needed !!!
  2. The property “Level” does not exist in the corresponding database table. This property is filled by calculating the hierarchy while selecting the category data. To avoid errors when inserting or updateing with PetaPoco (PetaPoco generates a full SQL statement with every defined property of the info class), this property should have the attribute [IgnoreColumn] !

The Controller Class

Simple Data Access with PetaPoco GetById

All the data access logic should now be defined In the controller class. Like in the example above I ‘ll begin with the categories, because their access methods are simpler than the faq ones. Lets start with the “get” method:

public CategoryInfo GetCategory(int? faqCategoryId)
{
	CategoryInfo category;
	using (IDataContext ctx = DataContext.Instance())
	{
		var rep = ctx.GetRepository<CategoryInfo>();
		category = rep.GetById(faqCategoryId);
	}
	return category;
}

A fast test shows a wonderful successful result! So we could kick out a lot of obsolete code:

DataProvider:

public abstract IDataReader GetFAQ(int faqId, int moduleId);</pre>

SqlDataProvider:

public override IDataReader GetCategory(int faqCategoryId, int moduleId)
{
	return ((IDataReader) (SqlHelper.ExecuteReader(ConnectionString, DatabaseOwner + ObjectQualifier + "FAQCategoryGet", faqCategoryId, moduleId)));
}

and the code for the stored procedure:

/* -------------------------------------------------------------------------------------
/   FaqCategoryGet
/  ------------------------------------------------------------------------------------- */

IF exists (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'{databaseOwner}{objectQualifier}FAQCategoryGet') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROCEDURE {databaseOwner}[{objectQualifier}FAQCategoryGet]
go

CREATE PROCEDURE {databaseOwner}[{objectQualifier}FAQCategoryGet]
(
	@FaqCategoryId int,
	@moduleId int 
)
AS
SELECT
	[FaqCategoryId],
	[ModuleId],
	CASE WHEN [FaqCategoryParentId] IS NULL THEN 0 ELSE [FaqCategoryParentId] END AS [FaqCategoryParentId],
	[FaqCategoryName],
	[FaqCategoryDescription],
	0 As [Level],
	[ViewOrder]
FROM {databaseOwner}[{objectQualifier}FAQsCategory]
WHERE
	[FaqCategoryId] = @FaqCategoryId
	AND [moduleid]=@moduleId 

GO

So far everything went fine!

Data Access with SQL Select

Next goal is the method “ListCategories” which should result in a list of categories from the database (wow! never had expected this !). This method has a parameter “onlyUsedCategories”. If true, this method returns only categories which have a minimum of one corresponding record in the faqs table.

The original stored procedure looked like this:

/* -------------------------------------------------------------------------------------
/   FaqCategoryList 
/  ------------------------------------------------------------------------------------- */

if exists (select * from dbo.sysobjects where id = object_id(N'{databaseOwner}{objectQualifier}FAQCategoryList') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROCEDURE {databaseOwner}[{objectQualifier}FAQCategoryList]
GO

CREATE PROCEDURE {databaseOwner}[{objectQualifier}FAQCategoryList]
(
	@ModuleID	int,
	@OnlyUsedCategories   bit
)
AS
SELECT
	[FaqCategoryId],
	[ModuleId],
	CASE WHEN [FaqCategoryParentId] IS NULL THEN 0 ELSE [FaqCategoryParentId] END AS [FaqCategoryParentId],
	[FaqCategoryName],
	[FaqCategoryDescription],
	0 As [Level],
	[ViewOrder]
FROM {databaseOwner}[{objectQualifier}FAQsCategory]
WHERE [ModuleId] = @ModuleId
AND ([FaqCategoryId] IN (SELECT CategoryId FROM {databaseOwner}[{objectQualifier}FAQs]) OR @OnlyUsedCategories=0)

GO

because of the subselect in the where clause we could not use the PetaPoco standard syntax for querying. This would look similar to this:

public static IEnumerable<CategoryInfo> ListCategories(int moduleId)
{
	IEnumerable<CategoryInfo> cats;
	using (IDataContext context = DataContext.Instance())
	{
		var repository = context.GetRepository<CategoryInfo>();
		cats = repository.Find("WHERE ModuleID = @0", moduleId);
	}
	return cats;
}

First idea was to expand the where clause by using the subselect:

cats = repository.Find("WHERE ModuleID = @0 AND FaqCategoryId IN (SELECT CategoryId FROM {databaseOwner}[{objectQualifier}FAQs]) OR @1=0)", moduleId);</pre>

Indeed there is a problem with this! The variables  {databaseOwner} and {objectQualifier} are not replaced by the DAL! I’ve created a workaround for this (see next paragraph for more details **!)[Edit: see annotations at the end of this blog!]**  For now, I use the PetaPoco ExecuteQuery syntax to get my data:

<pre class="brush: csharp; auto-links: true; collapse: false; first-line: 1; gutter: true; html-script: false; light: false; ruler: false; smart-tabs: true; tab-size: 4; toolbar: true;">public IEnumerable<CategoryInfo> ListCategories(int moduleId, bool onlyUsedCategories)
{
	IEnumerable<CategoryInfo> categories;
	string sql = "SELECT [FaqCategoryId], [ModuleId]," +
		" CASE WHEN [FaqCategoryParentId] IS NULL THEN 0 ELSE [FaqCategoryParentId] END AS [FaqCategoryParentId]," +
		" [FaqCategoryName], [FaqCategoryDescription],0 As [Level],[ViewOrder]" +
		" FROM {databaseOwner}[{objectQualifier}FAQsCategory] " +
		" WHERE [ModuleId] = @0" +
		" AND ([FaqCategoryId] IN (SELECT CategoryId FROM {databaseOwner}[{objectQualifier}FAQs]) OR @1=0)";
	sql = ReplaceSqlPlaceholders(sql);
	using (IDataContext ctx = DataContext.Instance())
	{
		categories = ctx.ExecuteQuery<CategoryInfo>(CommandType.Text, sql, moduleId, onlyUsedCategories);
	}
	return categories;
}

Replace SQL Tokens

This topic was the hardest nut to crack because nobody could help with this. The controller class does not know the variables objectQualifier and databaseOwner. These are defined in web.config dependent on the dataprovider type:

<data defaultProvider="SqlDataProvider">
   <providers>
     <clear />
     <add name="SqlDataProvider" 
     	type="DotNetNuke.Data.SqlDataProvider, DotNetNuke" connectionStringName="SiteSqlServer" 
     	upgradeConnectionString="" 
     	providerPath="~\Providers\DataProviders\SqlDataProvider\" 
     	objectQualifier="dnn" 
     	databaseOwner="dbo." />
   </providers>
</data>

To retrieve this information in a proper way, we need to pass it from the SqlDataProvider to our controller. By default only the SqlDataProvider knows these properties. To retrieve them in a regular way we need to add them as virtual properties in the abstract DataProvider class…

public virtual string ObjectQualifier { get; set; }
public virtual string DatabaseOwner { get; set; }

… and in SqlDataProvider mark these as "override":

public override string ObjectQualifier
{
	get
	{
		return _objectQualifier;
	}
}

Now we are able to access these two properties in our controller class and can replace the placeholders at runtime with the appropriate values:

private string ReplaceSqlPlaceholders(string sql)
{
	return sql
		.Replace("{objectQualifier}", DataProvider.Instance().ObjectQualifier)
		.Replace("{databaseOwner}",DataProvider.Instance().DatabaseOwner);
}

But (as mentioned before) this is only a workaround and should be fixed as far as possible in the core (e.g. in PetaPocoDataContext.cs) !

The core team has implemented the objectQualifier and databaseOwner replacement into the core since 7.0.1, so this chapter is obsolete now!

CRUD Methods

After cracking this big hunk the next steps were running easy:

public int AddCategory(CategoryInfo category)
{
	using (IDataContext ctx = DataContext.Instance())
	{
		var rep = ctx.GetRepository<CategoryInfo>();
		if (category.FaqCategoryParentId == 0)
			category.FaqCategoryParentId = null;
		rep.Insert(category);
		return category.FaqCategoryId;
	} 
}

public void UpdateCategory(CategoryInfo category)
{
	using (IDataContext ctx = DataContext.Instance())
	{
		var rep = ctx.GetRepository<CategoryInfo>();
		if (category.FaqCategoryParentId == 0)
			category.FaqCategoryParentId = null;
		rep.Update(category);
	}
}

public void DeleteCategory(int faqCategoryId)
{
	using (IDataContext ctx = DataContext.Instance())
	{
		var rep = ctx.GetRepository<CategoryInfo>();
		rep.Delete("WHERE FaqCategoryId = @0",faqCategoryId);
	}
}

Stored Procedures

In the FAQ module exists a stored procedure which contains a relative complex sql statement for retrieving categories sorted by hierarchical structure:

WITH cte ([FaqCategoryParentId], [FaqCategoryId], [ModuleId], [FaqCategoryName], [FaqcategoryDescription], [Level], [ViewOrder], [Sort] )
AS
(
	-- Anchor member definition
	SELECT e.FaqCategoryParentId, e.FaqCategoryId, e.ModuleId,  e.FaqCategoryName, e. FaqcategoryDescription, 0 AS [Level], e.ViewOrder, 
		RIGHT(REPLICATE('0', 8) + CAST(e.ViewOrder AS VARCHAR(MAX)),8) as [Sort]
	FROM {databaseOwner}[{objectQualifier}FAQsCategory] AS e
	WHERE e.FaqCategoryParentId IS NULL
	UNION ALL
	-- Recursive member definition
	SELECT e.FaqCategoryParentId, e.FaqCategoryId, e.ModuleId, e.FaqCategoryName, e. FaqcategoryDescription, [Level] + 1 AS [Level], e.ViewOrder, 
	   CAST(d.Sort + '/' + 
			RIGHT(REPLICATE('0', 8) + CAST(e.FaqCategoryparentId AS VARCHAR(MAX)),8) + '-' +  
			RIGHT(REPLICATE('0', 3) + CAST(e.ViewOrder AS VARCHAR(MAX)) ,3)
	   AS VARCHAR(MAX)) AS [Sort]
	FROM {databaseOwner}[{objectQualifier}FAQsCategory] AS e
	INNER JOIN cte AS d
		ON e.FaqCategoryParentId = d.FaqCategoryId

)
-- Statement that executes the CTE
SELECT CASE WHEN cte.FaqCategoryParentId IS NULL THEN 0 ELSE cte.FaqCategoryParentId END AS [FaqCategoryParentId], [FaqCategoryId], [ModuleId], [FaqCategoryName], [FaqcategoryDescription], [Level], [ViewOrder]
FROM cte 
WHERE cte.ModuleId =  @ModuleId
AND (cte.FaqCategoryId IN (SELECT CategoryId FROM {databaseOwner}[{objectQualifier}FAQs]) OR @OnlyUsedCategories = 0)
order by [Sort]

I tried to do this in sql syntax too (like “Data Access with Sql Select”) but failed. The reason was that PetaPoco automatically appends a generated sql snippet (“SELECT field1, field2, field3…”) before our sql string (ExecuteQuery<CategoryInfo> arranges this). So in this case I did not convert the SP into controller code but leave it as  SP and access this SP from my controller class directly:

public IEnumerable<CategoryInfo> ListCategoriesHierarchical(int moduleId, bool onlyUsedCategories)
{
	IEnumerable<CategoryInfo> categories;
	string sql = "{objectQualifier}FAQCategoryListHierarchical";
	sql = ReplaceSqlPlaceholders(sql);

	using (IDataContext ctx = DataContext.Instance())
	{
		categories = ctx.ExecuteQuery<CategoryInfo>(CommandType.StoredProcedure, sql, moduleId, onlyUsedCategories);
	}
	return categories;
}

The problem with non working Common Table Expressions (cte syntax) is also solved in DNN 7.0.1.

This counts only for the SELECT case. There is another complex SP in the project which changes the vieworder of two FAQs. Here I could use the sql syntax in code with no problems:

public void ReorderFAQ(int faqId1, int faqId2, int moduleId)
{
	string sql = "WITH tmpSwappedOrder(ItemId,ViewOrder) AS" +
		" (" +
		"	SELECT @1 AS ItemId,ViewOrder FROM {databaseOwner}[{objectQualifier}FAQs] WHERE ItemId = @0" +
		"	UNION" +
		"	SELECT @0 AS ItemId,ViewOrder FROM {databaseOwner}[{objectQualifier}FAQs] WHERE ItemId = @1" +
		" )" +
		" UPDATE {databaseOwner}[{objectQualifier}FAQs] SET ViewOrder = (SELECT ViewOrder FROM tmpSwappedOrder s WHERE s.ItemId = {databaseOwner}[{objectQualifier}FAQs].ItemId)" +
		" WHERE {databaseOwner}[{objectQualifier}FAQs].ItemId IN (SELECT ItemId FROM tmpSwappedOrder);" +
		" " +
		" WITH tmpReorder(ViewOrder,ItemId) AS" +
		" (" +
		"	SELECT TOP 1000 row_number() OVER (ORDER BY f.ViewOrder) as rank, f.ItemId" +
		"	FROM {databaseOwner}[{objectQualifier}FAQs] f" +
		"	WHERE f.ModuleId = @2" +
		"	ORDER BY rank " +
		" )" +
		" UPDATE {databaseOwner}[{objectQualifier}FAQs] " +
		"	SET ViewOrder = (SELECT ViewOrder FROM tmpReorder r WHERE r.ItemId = {databaseOwner}[{objectQualifier}FAQs].ItemId)" +
		"	WHERE ModuleId = @2";
	sql = ReplaceSqlPlaceholders(sql);
	using (IDataContext ctx = DataContext.Instance())
	{
		ctx.Execute(CommandType.Text, sql,faqId1,faqId2,moduleId);
	}
}

Tables with Joins

In the FAQ project the data for the faqs had been assembled by a join to the category table. Name and description of the category were collected from the category table and the username of the last editor was collected from the user table. When working with PetaPoco this is not very practical. We need an info class including the joined columns, but for updateing and inserting they must be decorated as [IgnoreColumn]. The downside is, that they are not populated with data then …

So to get around this problem we could choose between two solutions. First we can create two info classes, one containing all the fields including the joined ones (for selections) and another one containing only with properties for the columns of the table (for CRUD operations).

Alternatively we have the opportunity to work only with the simple info class and collect the needed category information as an extra call when needed. Which solution is the best depends on the context – in the FAQ module I chosed the second solution. But if I need to loop with foreach through 300 FAQ records and have to retrieve for every single record the name and description of the category with another sql access, it could be better to choose the first solution…

Example in Token Replace:

before :

#region Implementation of IPropertyAccess

public string GetProperty(string strPropertyName, string strFormat, CultureInfo formatProvider, UserInfo AccessingUser, Scope AccessLevel, ref bool PropertyNotFound)
{

	PropertyNotFound = false;
	switch (strPropertyName.ToLower())
	{
		case "question":
			return PropertyAccess.FormatString(_question, strFormat);
		case "answer":
			return PropertyAccess.FormatString(_answer, strFormat);
		case "user":
			return PropertyAccess.FormatString(_createdByUserName, strFormat);
		case "viewcount":
			return _viewCount.ToString(String.IsNullOrEmpty(strFormat) ? "g" : strFormat, formatProvider);
		case "vieworder":
			return _viewOrder.ToString(String.IsNullOrEmpty(strFormat) ? "g" : strFormat, formatProvider);
		case "categoryname":
			return PropertyAccess.FormatString(_faqCategoryName, strFormat);
		case "categorydesc":
			return PropertyAccess.FormatString(_faqCategoryDescription, strFormat);
		case "datecreated":
			return _createdDate.ToString(String.IsNullOrEmpty(strFormat) ? "d" : strFormat, formatProvider);
		case "datemodified":
			return _dateModified.ToString(String.IsNullOrEmpty(strFormat) ? "d" : strFormat, formatProvider);
		case "index":
			return _index.ToString(String.IsNullOrEmpty(strFormat) ? "g" : strFormat, formatProvider);
		default:
			PropertyNotFound = true;
			return String.Empty;
	}
}

after:

#region Implementation of IPropertyAccess

public string GetProperty(string strPropertyName, string strFormat, CultureInfo formatProvider, UserInfo AccessingUser, Scope AccessLevel, ref bool PropertyNotFound)
{

	PropertyNotFound = false;
	FAQsController faqController;
	switch (strPropertyName.ToLower())
	{
		case "question":
			return PropertyAccess.FormatString(Question, strFormat);
		case "answer":
			return PropertyAccess.FormatString(Answer, strFormat);
		case "user":
			UserInfo user = UserController.GetUserById(PortalSettings.Current.PortalId, Convert.ToInt32(CreatedByUser));
			return PropertyAccess.FormatString(user.DisplayName, strFormat);
		case "viewcount":
			return ViewCount.ToString(String.IsNullOrEmpty(strFormat) ? "g" : strFormat, formatProvider);
		case "vieworder":
			return ViewOrder.ToString(String.IsNullOrEmpty(strFormat) ? "g" : strFormat, formatProvider);
		case "categoryname":
			faqController = new FAQsController();
			return PropertyAccess.FormatString(faqController.GetCategory(CategoryId).FaqCategoryName, strFormat);
		case "categorydesc":
			faqController = new FAQsController();
			return PropertyAccess.FormatString(faqController.GetCategory(CategoryId).FaqCategoryDescription, strFormat);
		case "datecreated":
			return CreatedDate.ToString(String.IsNullOrEmpty(strFormat) ? "d" : strFormat, formatProvider);
		case "datemodified":
			return DateModified.ToString(String.IsNullOrEmpty(strFormat) ? "d" : strFormat, formatProvider);
		case "index":
			return Index.ToString(String.IsNullOrEmpty(strFormat) ? "g" : strFormat, formatProvider);
		default:
			PropertyNotFound = true;
			return String.Empty;
	}
}

LINQ and Friends

One of the big pros of the new DAL 2 is the possibility to work with Linq on the result sets. In faq module I haven’t used this, but for completeness I want to link to the DNN announcement module, which is ported by EricVB before:

public static IEnumerable<AnnouncementInfo> GetAnnouncements(int moduleId, DateTime startDate, DateTime endDate)
{
	IEnumerable<AnnouncementInfo> announcements;

	using (IDataContext context = DataContext.Instance())
	{
		var repository = context.GetRepository<AnnouncementInfo>();
		announcements =  repository.Find("WHERE ModuleID = @0 AND ( ((PublishDate >= @1) OR @1 IS NULL) AND ((PublishDate <= @2) OR @2 IS NULL) )",
                        moduleId, Null.GetNull(startDate, DBNull.Value), Null.GetNull(endDate, DBNull.Value))
                        .OrderBy(a => a.ViewOrder)
                        .ThenByDescending(a => a.PublishDate);
            }
            return announcements;
        }
}

Note: Eric has not used the null value handling of PetaPoco and works instead with Null.GetNull() converted values.

Summary

With the DAL 2 data layer we developers finally got a long-overdue modernization of data access. Except for the above-mentioned drawback with the wildcard replacement we now can work wonderfully simple with this, and above all, we don’t have to get our fingers bloody while typing tons of unneccessary code. If you know the difficult parts (and some of them I mentioned above), module development is a lot faster and cleaner now. And with the “Cacheable” –data annotation we get the extra benefit of automatic caching  without the need to write a single line of code. The performance of DAL 2 should be nearly the same as before (have not checked this). So ladys and gentleman: Start with DAL 2 today, you have my unconditional recommendation!

One last thing: If you see an error in this blog or have some annotations and/or corrections please feel free to add a comment! I’m happy about every… ok nearly every feedback ;-)

 

Back
Total: 56 Comment(s)
tkvnsdk
thanks
Wednesday, May 9, 2018 · reply ·
Cope Techs
Hacking is very much easy as it is done in the movies we watch, Hackers are really able to do all those stuffs we see, like breaking into a 💻 network to gain access to certain files, to steal those file, monitor users actives, delete files and lots more. HOW HACKING REALLY WORKS Hacking is easily done by the use of specific programs (Computer virus, Spywares, Trojans, Malwares) to gain access and control over a 💻 and Cell 📲 . These programs are designed to break into computer defense without users even knowing it. COPE TECHS SERVICES COPE TECHS is an organization you can trust and contact if you need Hacking services. Our aim is not to help individuals who have technological problems and not for Thefts purposes. We guarantee you a 💯 % assurance of our job and for no reason will our work for you be exposed to someone else. Prove of our works in the past will be show to you but please note that the individuals we did them for won’t be disclosed to you. HACKING SERVICE WE CAN OFFER YOU There is no limits to what we can do for you but we have a restriction in things we can do. For no reason will we help you in Robbery or Stealing if someone’s else belongings and properties, please put that in mind. Here is a list of our Approved services-: *Hacking of Phones 📱 or Computer 💻 for the purpose of monitoring Emails📧, Text messages✉️, Phone calls📞, Social Media Chats💝, if suspected your spouse is cheating or doing something you not okay with. *Repayments of Debts💵 to Banks and other Loan Companies, Yes with the help of Bitcoin💰 mining we have helped so many individuals repay debts. *Changing of University Grades for students🗞, we can help with this because we know how hard studying can be and we all know that without good grades individuals are limited to the kind of job they can secure. and lots more. If you don’t seem to find a Service you need feel free to ask us and if it’s not against our rules we will help you with it. Contacts is made only by email this helps us stay safe and untraceable. Contact-: copetechs@gmail.com
Saturday, May 25, 2019 · reply ·
free robux code
You will not need human verification and survey to produce Robux. You will only need to press the button for ‘generate code’.
Monday, June 25, 2018 · reply ·
 David Bennett
https://aroundandroid.com/ps3-emulator-for-android/">Check PS3 Emulator for Android on Around Android
Saturday, July 7, 2018 · reply ·
assignment helper malaysia
Our Writers offer the best assignment help at cheap price with 100% plagiarism free work, order now to get Upto 30% Off on assignment writing services in Malaysia! Visit now- https://myassignmenthelp.com/my/
Thursday, September 13, 2018 · reply ·
nursing assignment help
Hi this is Olivia Crew and i am SEO Expert in a reputed company livewebtutors. you are providing very informative and knowledgeable article so thank you for sharing with us. such a nice article. https://www.livewebtutors.com/assignment-help/nursing"> nursing assignment help
Friday, September 14, 2018 · reply ·
Assignment Help
This post is not just informative but impressive also. The post is so convincing that it created an urge to choose Assignment help services. You can email us at Info@Myassignmenthelpau.Com or Phone Number: +61-2-8005-8227 https://www.myassignmenthelpau.com/
Saturday, September 15, 2018 · reply ·
Assignment Help
A high-standard post with all imperative information about Assignment Help UK services. Looking forward to availing the premium services.
Saturday, September 22, 2018 · reply ·
Assignment Help
Thanks for sharing such a nice piece of information to us. This is very knowledgeable for me. I am John and i am offering Assignment Help to students of Australia and all over the world.
Wednesday, October 24, 2018 · reply ·
Assignment Help
Blog Commenting: An unmatched and nonpareil post i have ever seen. The content is so appealing that it has created an impulse to avail Assignment Help Singapore services.
Wednesday, October 31, 2018 · reply ·
Assignment Help
Blog Commenting: An unmatched and nonpareil post i have ever seen. The content is so appealing that it has created an impulse to avail Assignment Help Singapore services.Visit us : http://www.myassignmenthelpsg.com/
Wednesday, October 31, 2018 · reply ·
Reliable Writer
Your blog is very unique. I like it. Are you looking for https://www.digi-plus.co.ke/online-sales-development-help/help-with-product-review">Product review writing service? Look no more and contact us because we offer the best https://www.digi-plus.co.ke/content-management/best-product-review-writers">Product review writing services.
Monday, November 5, 2018 · reply ·
Correct my paper
It has been such a great adventure through your post. Awesome
Tuesday, November 6, 2018 · reply ·
Coursework editing service
Your post is among the best pages I have been to so far. https://www.customwritingbay.ca/canadian-writers/hire-coursework-editors
Tuesday, November 6, 2018 · reply ·
dissertation data analysis services
Tuesday, November 6, 2018 · reply ·
Reliable professional
Friday, November 9, 2018 · reply ·
Reliable professionals
Your blog post is elaborate and presentable. Keep it up. https://www.petrianeditingservice.co.uk/rewriting-help/proofing-aid
Friday, November 9, 2018 · reply ·
Dissertation literature review help
GREAT ARTICLE
Saturday, November 17, 2018 · reply ·
dissertations writer
Educative post. Keep up the good work. Are you looking for Masters literature review help or MBA dissertation help. We will help you.
Saturday, November 17, 2018 · reply ·
Assignment Help
Get best assignment help in Australian by the team of professional assignment helper at My Assignment Help OZ. To know more visit: https://myassignmenthelpoz.com/
Saturday, November 17, 2018 · reply ·
Nursing assignment help
Hello, I am Ava Watson, a student of engineering. I love reading and writing blogs which is related to the knowledge. Your blog is very informative. I am also a part time writer and working in a company in Australian assignment help who offer nursing assignment help to Australian students.
Wednesday, November 21, 2018 · reply ·
panistefanin
Os doy las gracias por la información! Yo estaba buscando y no podía encontrar. Usted me ayudó! https://collegeadmissionscores.com/kean-university/
Wednesday, December 26, 2018 · reply ·
secure your teen
Incredible post. This article is exceptionally useful for the general population and furthermore for me. I get the some helpful information in this post. A debt of gratitude is in order for sharing the instructive point. https://www.needynews.com/tech/android/parental-control/
Thursday, February 7, 2019 · reply ·
Jessica Jones
Get assignment help in Australia provided by Australian Assignment Help at: https://australianassignmenthelp.com
Friday, February 8, 2019 · reply ·
Essay Typer
Every single student is searching for a proper essay generator who can understand the need of the work and do the essay completely flawless. However, it often happens that students fail to figure out who can be one of the best instant essay typer for their work. Academic essay writing has been a parameter to judge the merits of the students for a long time. The present time is none the less. In fact, as time is passing by, students are having more and more requirements to write an essay that will be graded. Hence seeking do my essay help is on the rise and it will be.
Friday, February 15, 2019 · reply ·
Assignment Help
A special thanks to Assignment Help Australia for providing an exceptional assignment within a short period of time. Your work is really amazing. Hope you reach heights in the future. You can email us at cs@Myassignmenthelpau.Com or Phone Number: +61-2-8005-8227
Wednesday, February 27, 2019 · reply ·
painters red deer
Nice blog! Such an amazing and helpful post this is. I really really love it. It's so good and so awesome. https://paintingrd.com
Thursday, April 11, 2019 · reply ·
Zoro oroz
I appreciate you spending some time and effort to put this short article together.I once again find myself spending a significant amount of time both reading and commenting. https://www.wordypen.com/best-parental-control-app-to-lock-your-kids-smartphone/
Friday, April 12, 2019 · reply ·
James12@gmail.com
Sample Assignment provides the assignment help to the students of various universities at a low price. Our highly-experienced writers are available 24x7 for guiding the students for any academic help. Email us your details at info@sampleassignment.com! https://www.sampleassignment.com/
Saturday, April 27, 2019 · reply ·
Smith
Sample Assignment bestows over the college going students with online assignment help. It is a consultancy possessing academic experts providing a number of subject-specific assignment helps. Management assignment help, economics assignment help, MATLAB assignment Help, MySQL assignment help, marketing assignment help, etc. https://www.sampleassignment.com/marketing-assignment-help.html
Saturday, April 27, 2019 · reply ·
bret hart jacket
this blog has been giving us and wonderful points always about is fitting gutter grids or gutter hedgehog really worth it?. thanks for this deadpool motorcycle jacket blog for sharing and giving us better info always.
Friday, May 3, 2019 · reply ·
bret hart jacket
The information that is provided on your website is amazing and I am always eager to catch hold of the new posts being published on your website, because of this i use to updated, thanks for sharing this wonderful article. https://americasuits.com/wwe-bret-hart-hitman-black-leather-jacket
Friday, May 3, 2019 · reply ·
Andy Wilson
Wonderful post. I really enjoyed it while reading. Get premium assignment services from Student Assignment Help, to finish your assignment before the deadline.
Friday, May 17, 2019 · reply ·
Andy Wilson
Wonderful post. I really enjoyed it while reading. Get top-class assignment writing services from Student Assignment Help, to finish your assignment before the deadline. Visit our website: https://www.studentassignmenthelp.com/Do-Assignment-for-Me
Friday, May 17, 2019 · reply ·
Sophie Miller
Ich danke Ihnen für die Information! Ich war auf der Suche nach und konnte nicht finden. Du hast mir geholfen! https://19216801.mobi/
Tuesday, May 21, 2019 · reply ·
Cope Techs
Hacking is very much easy as it is done in the movies we watch, Hackers are really able to do all those stuffs we see, like breaking into a 💻 network to gain access to certain files, to steal those file, monitor users actives, delete files and lots more. HOW HACKING REALLY WORKS Hacking is easily done by the use of specific programs (Computer virus, Spywares, Trojans, Malwares) to gain access and control over a 💻 and Cell 📲 . These programs are designed to break into computer defense without users even knowing it. COPE TECHS SERVICES COPE TECHS is an organization you can trust and contact if you need Hacking services. Our aim is not to help individuals who have technological problems and not for Thefts purposes. We guarantee you a 💯 % assurance of our job and for no reason will our work for you be exposed to someone else. Prove of our works in the past will be show to you but please note that the individuals we did them for won’t be disclosed to you. HACKING SERVICE WE CAN OFFER YOU There is no limits to what we can do for you but we have a restriction in things we can do. For no reason will we help you in Robbery or Stealing if someone’s else belongings and properties, please put that in mind. Here is a list of our Approved services-: *Hacking of Phones 📱 or Computer 💻 for the purpose of monitoring Emails📧, Text messages✉️, Phone calls📞, Social Media Chats💝, if suspected your spouse is cheating or doing something you not okay with. *Repayments of Debts💵 to Banks and other Loan Companies, Yes with the help of Bitcoin💰 mining we have helped so many individuals repay debts. *Changing of University Grades for students🗞, we can help with this because we know how hard studying can be and we all know that without good grades individuals are limited to the kind of job they can secure. and lots more. If you don’t seem to find a Service you need feel free to ask us and if it’s not against our rules we will help you with it. Contacts is made only by email this helps us stay safe and untraceable. Contact-: copetechs@gmail.com
Saturday, May 25, 2019 · reply ·
Online Assignment Help Australia
When it comes to your career prospects and bright future, Online Assignment Help Australia takes the onus on itself to promote your growth in the right direction.
Sunday, May 26, 2019 · reply ·
mariamith
They are highly qualified and skilled professional writers who have vast experience in writing assignments, dissertations, essays, research papers, term papers etc. https://onlineassignmenthelpaustralia.com/
Sunday, May 26, 2019 · reply ·
Auto Detailing Red Deer
Nice post. I really like this article. Because it’s very clear and awesome. https://detailingreddeer.com
Saturday, June 22, 2019 · reply ·
Pest Control Henderson, NV
Valuable information and insights here. I've seen so many articles, but definitely, this has been the best I’ve read!  https://pestcontrol-henderson--nv.com" target="_blank">Pest Control Henderson, NV
Tuesday, June 25, 2019 · reply ·
Fireplace Repair Calgary
I really enjoy reading all of your weblogs.You rock and please keep up the good work!
Tuesday, June 25, 2019 · reply ·
https://riverside-cabinets-pro.com
Nice post. I really like this article. Because it’s very clear and awesome. https://riverside-cabinets-pro.com
Friday, June 28, 2019 · reply ·
Assignment Help
If you want to hire the best experts for assignment help in the UK, then visit the AbAssignmentHelp company's website, where you will find the best assignment support providers and the writers.
Monday, July 8, 2019 · reply ·
bruce david
Hello everyone..Welcome to my free masterclass strategy where i teach experience and inexperience traders the secret behind a successful trade.And how to be profitable in trading I will also teach you how to make a profit of $12,000 USD weekly and how to get back all your lost funds feel free to email me on(brucedavid004@gmail.com) or whataspp number is +22999290178
Sunday, July 14, 2019 · reply ·
David James
Awesome article, it was exceptionally helpful! I simply began in this and I'm becoming more acquainted with it better! keep doing awesome! https://www.givology.org/~ludochat1/
Tuesday, July 16, 2019 · reply ·
priya
Great post. ThIS IS unique information. We connect you live to chat with strangers making it easier than ever for you to meet new people online. https://me2call4uonline.doodlekit.com/home
Wednesday, July 17, 2019 · reply ·
Masonry Victoria
Posts are great. Thank you for sharing your ideas. This post gives truly quality information. https://surveyors-victoriabc.ca
Wednesday, July 24, 2019 · reply ·
Amanda
Good informative post. Thanks for sharing your knowledge. https://bathroomrenovationslondon.ca
Thursday, July 25, 2019 · reply ·
Assignment Help Australia
gotoassignmenthelp.com.au is the leading assignment help provider worldwide. We have an exclusive services for assignment help australia for students. We have a group of online assignment help experts from top universities to help these students in the best possible manner. Each of these academic writers possesses extensive knowledge and expertise.
Wednesday, July 31, 2019 · reply ·
helly
gotoassignmenthelp.com.au is the leading assignment help provider worldwide. We have an exclusive services for assignment help australia for students. We have a group of online assignment help experts from top universities to help these students in the best possible manner. Each of these academic writers possesses extensive knowledge and expertise. https://www.gotoassignmenthelp.com.au/
Wednesday, July 31, 2019 · reply ·
isbmdevang
Monday, August 5, 2019 · reply ·
Assignment Help
A very high-level post with a piece of knowledgeable information.Thank you for sharing such information if you need any college-level Assignment Help at reliable quality with better work. kindly visit us or Whatsapp +61 2 80113341
Tuesday, August 6, 2019 · reply ·
Carpet-Cleaners-Las-Vegas
Good post. https://carpetcleaning-lasvegas-nv.com/">Carpet-Cleaners-Las-Vegas
Friday, August 9, 2019 · reply ·
Carpet-Cleaners-Las-Vegas
https://carpetcleaning-lasvegas-nv.com/">Carpet-Cleaners-Las-Vegas https://carpetcleaning-lasvegas-nv.com" target="_blank">Carpet-Cleaners-Las-Vegas
Friday, August 9, 2019 · reply ·
ALBERT KAYLOR
Welcome. BE NOT TROUBLED anymore. you’re at the right place. Nothing like having trustworthy hackers. have you lost money before or bitcoins and are looking for a hacker to get your money back? You should contact us right away it’s very affordable and we give guarante to our clients. Our hacking services are as follows: -hack into any nkind of phone _Increase Credit Scores _western union, bitcoin and money gram hacking _criminal records deletion _Hacking of phones(that of your spouse, boss, friends, and see whatever is being discussed behind your back) _Security system hacking...and so much more. Contact THEM now and get whatever you want at Superior.hack@gmail.com +16692252253 IT HAS BEEN TESTED AND TRUSTED
Monday, August 12, 2019 · reply ·

about.me.

Torsten WeggenMy name is Torsten Weggen and I am CEO of indisoftware GmbH in Hanover, Germany. I'm into DNN since 2008. Before this, I did a lot of desktop stuff mainly coded with Visual Foxpro (see http://www.auktionsbuddy.de). 

I'm programmer, husband, father + born in 1965.

Please feel free to contact me if you have questions.

Latest Posts

DNN module development with Angular 2+ (Part 7)
6/10/2018 1:43 PM | Torsten Weggen
DNN module development with AngularJS (Part 6)
12/16/2016 7:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 5)
12/16/2016 6:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 4)
12/16/2016 5:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 3)
12/16/2016 4:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 2)
12/16/2016 3:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 1)
12/15/2016 7:19 AM | Torsten Weggen
Blogging in DNN with Markdown Monster by Rick Strahl
11/27/2016 1:14 PM | Torsten Weggen
Creating a global token engine
11/18/2016 10:25 AM | Torsten Weggen
DnnImagehandler - Hot or not ?
2/21/2015 11:52 PM | Torsten Weggen

My Twitter

Torsten Weggen 3/17/2019

You can make a real difference in Abir ’s life. Join me on @Kiva https://t.co/NlCTgIAZAN

Torsten Weggen 2/2/2019

As a freelance developer I often get invites from headhunters. This one should be very interesting... https://t.co/CNrEXBTBuJ

Torsten Weggen 10/11/2018

Evernote or OneDrive user and love #markdown #markdownmonster ? Take a look at Joplin! https://t.co/4bzkiD6CHn