Friday, July 14, 2006

Editing Web.config file from code behind


Hi friends,
Some times you might have thought that it would make things convenient to write back to your .NET application's config file. The framework provides simple methods for reading from the config file, but gives you nothing for writing values back to the config file. It is easy enough to write values back to the file. It's only XML file.

I am using framework 2.0. Let’s we will make one application in ASP.NET. Our application contains the “web.config” file and our application aim is to modify the config file. For that we will add one key in “appsetting” section of the config file as shown below. We will allow application to change the value of the key “connectionstring” from “sandy” to “testapplication”.

Change/ add in web.config:

<appSettings>
<add key="ConnectionString" value="sandy" />
</appSettings>

To write values back, you just need to open the config file as an XmlDocument and write away. No big deal. You can add name/value pairs, remove elements, and modify them or whatever. Copy the code and paste it in the codebehind of the page. This code will allow you to change “appSettings” section of the config file based on the key name.

For this you need to make the aspx page for the front end. With the one text box demanding for the value to change and submit button.

Code is given below:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

using System.Xml;

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}
protected void Button1_Click(object sender, EventArgs e)
{
string ConnectionString;
ConnectionString = txtvalue.Text.ToString(); // or else you can take the parameters from the form
//and create your own connectionstring
// like string ConnectionString = @"Server=local;Database=" + txtDatabaseName.Text + ";Trusted_Connection=true";

// set the web.config path to read / change
string path = Server.MapPath("Web.config"); // root web.config

// we will search for the appSetting entity
XmlDocument xDoc = new XmlDocument();

xDoc.Load(path); // load the web.config

XmlNodeList nodeList = xDoc.GetElementsByTagName("appSettings");

XmlNodeList nodeAppSettings;

XmlNode node;

// search the key
nodeAppSettings = nodeList[0].ChildNodes; // get all the nodes present under
// appSetting

node = nodeList[0].ChildNodes[0]; // take the First node i.e. [add] node with [key name] ConnectionString
// [Add] - Node name
// [Key] - Attribute Name
// [Value] - Attribute Name

//int nnodes;
//for (nnodes = 0; nnodes <= nodeList.Count - 1; nnodes++)
//{
// if(nodeList[0].ChildNodes.
// nodeAppSettings = nodeList[0].ChildNodes;
//}

XmlAttributeCollection xmlAttCollection = node.Attributes;

// you can even change the key name
//xmlAttCollection[0].InnerXml = txtKey.Text; // for key attribute
xmlAttCollection[1].InnerXml = ConnectionString.ToString(); // for value attribute

// Writting Web.config file
xDoc.Save(path); // saves the web.config file
}
}



Note: it is not good to write back to the config file. The framework does not include this ability for a reason.

Monday, July 10, 2006

Membership Provider Control in ASP.Net


Hi Friends,

Here I am demonstrating how use membership provider control for login purposes. Microsoft has provided the login control that enables the developer to create the functionality of authorizing the user quickly. When I was developing this for site I encounter some problem. That also I am going to discuss over here.

First when you start to implement the membership provider control into your site. You need to decide which pages or which folder you want to restrict the user unless he/she is authorized. After deciding this, create your pages or pages in folder.
Here I have created a page PriceListing.aspx and I want to restrict the anonymous user to access it. User either has to sign in or Sign Up to view the page. I have also created the pages default.aspx, MemberLogin.aspx and CreateUser.aspx.

Default.aspx is the default page for site. If user is already logged in then the user status will be logout else login. This was related to UI. Not come to the configuration membership provider control to use the login facility provided by the Microsoft in VS.NET 2005.

In web.config on has to do the following changes.

1. Create the connection string which will point to the database.
<connectionStrings>
<add name="MPConnectionString" connectionString="Data Source=.\SQLEXPRESS; AttachDbFilename=DataDirectory\ASPNETDB.MDF; Integrated Security=True;User Instance=True" providerName="System.Data.SqlClient" />

<!--<add name="MPConnectionString" connectionString="Data Source=.\SQLExpress;Persist Security Info=True;Integrated Security=SSPI;Initial Catalog=TestDatabase"/>
-->
</connectionStrings>

Here you can see two types of connection string

         1. In first connection string
AttachDbFilename=DataDirectory\ASPNETDB.MDF
This line automatically creates the database names as ASPNetDB.mdf in to your site/App_Data folder. It also automatically creates the tables into it which are required for membership controls.
         2. in second connection string you are specifying your own database for provider, hence it does not have the membership provider tables in it. You can create that using aspnet_regsql.exe utility. (please do not forget to give rights for membership provider to the public role )


2. Write the following statements in <system.web>.

<identity impersonate="true"/>
<membership defaultProvider="MemberShipSandeepTest">
<providers>
<add connectionStringName="MPConnectionString" name="MemberShipSandeepTest" type="System.Web.Security.SqlMembershipProvider" />
</providers>
</membership>

Here we have create the membership provider named as MemberShipSandeepTest. Specified connection string as MPConnectionString. Specified type as System.Web.Security.SqlMembershipProvider.

Automatically Microsoft provides the default provider we have to change the default provider to our newly created provider. This can be done very easily by specifying one of the attribute of the membership as defaultProvider="MemberShipSandeepTest" our provider name.

3. Next step is to define the authentication and authorization settings.

<authentication mode="Forms">
<forms cookieless="UseCookies" name="AuthCookie" defaultUrl="Default.aspx" loginUrl="MemberLogin.aspx" protection="All" timeout="10">
</forms>
</authentication>

Make the authentication mode as forms
There are total 4 types of authentication Windows, Forms, Passport, None. We are going to use Forms Authentication.
If Authentication mode is Forms we have to specify 2 things mainly:

· Cookieless = specify the “UseCookies” since we required the cookies for authorization of users.
· defaultUrl = user is redirected to this URL if he/she is directly want to login. And no return URL is present.
· loginUrl = if user is not authenticated and not authorized to access the page visited then system automatically redirects the user to this page.

4. Next step allow all user to access all the pages in site by specifying

<authorization>
<allow users="*"/>
</authorization>

Where * denotes all users.

5. Next step is to restrict the user to some pages or the folders.
To restrict the user before logging in to site can be possible from the web.config. you just have to write few lines in web.config as specified below.

In our example we want to restrict the unauthorized user to access the PriceListing.aspx
Write below line outside <system.web>.

<location path="PriceListing.aspx">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>

? Denotes the anonymous user.
In path you can also specify the folder path. Specifying the folder path allows you to impose the authorization for all pages in side the folder.



You can download the example as per the above discussion…..
http://aspspider.net/sandeeppawar/ArticlesItems/AD_11_LoginControl.zip
(You can customize the directions like continue button when user sign up. Log out click.)

SQL - Recursive Cursor with tree structure data


This article aims for the same result but keeps your table as simple as possible. It also allows us to do some pretty neat things with recursive stored procedures. My examples try to cover both posts and the hierarchical numbering scheme from the question. It should work for any generic "expanding hierarchy" problem.

We'll start with a table that looks like this: *

=====================================================================

CREATE TABLE [Posts] (
[PostID] [int] IDENTITY (1, 1) NOT NULL ,
[Subject] [char] (50) NULL ,
[ParentID] [int] NULL ,
[PostSortKey] [datetime] NOT NULL
)
GO

ALTER TABLE [Posts] WITH NOCHECK
ADD CONSTRAINT [DF_Posts_SortKey] DEFAULT (getdate()) FOR [PostSortKey]
GO *

=====================================================================


The PostID column is our primary key for this table. Subject is the subject of the post. For simplicity sake I removed all other post related fields that were not absolutely necessary such as author and the body of the post. ParentID is the PostID of the post for the parent. If this post is a top level post, then this field will be zero. PostSortKey is the sort order for the posts. This will usually be a datetimefield. In my example here, I used a default of GETDATE() to populate the field. In the specific example of this question it could be whatever the user wanted.

My example will use a dataset that looks like this: *

PostID Subject ParentID PostSortKey
1 First Post 0 2000-12-06 20:54:29.407
2 Second Post 0 2000-12-06 20:54:29.407
3 Child of First Post 1 2000-12-06 20:54:29.407
4 Child of Second Post 2 2000-12-06 20:54:29.407
5 Child of First Child 3 2000-12-06 20:54:29.407
6 Another FP Child 1 2000-12-06 21:04:49.217
7 Smallest Kid 5 2000-12-06 21:18:49.203
8 Another Munchkin 3 2000-12-06 21:28:22.040
*

Populating a table like this should be very easy. You just need to insert a record with its parent ID.

This example is composed of two pieces of code. The first is a SQL Script to create a temporary table and call the stored procedure. It looks like this: *

===========================================================


Create Table #NestedPosts (
SortID int IDENTITY (1,1), PostID int, PostKey varchar(200), PostLevel int
)*
*

exec getchildren 0, 1, ''

*

SELECT
SortID, P.PostID, ParentID,
PostLevel,
PostKey = LEFT(PostKey, 10),
PostSortKey = convert(varchar(19), PostSortKey, 120),
Subject = LEFT( SPACE( (PostLevel-1) * 2 ) + Subject , 40)
FROM
#NestedPosts N JOIN Posts P
ON N.PostID = P.PostID
Order by
SortID

*

DROP TABLE #NestedPosts

===========================================================


The temporary table is populated by the stored procedure. The final SELECT prints out the results. Next is the stored procedure. It looks like this: *

===========================================================

CREATE PROC GetChildren
(
@ParentID int, @PostLevel int, @ParentPostKey varchar(200)
)
AS

BEGIN

SET NOCOUNT ON
DECLARE @NextLevel int, @Counter int, @PostKey varchar(200)
SET @Counter = 1

-- Build a cursor to loop through all the kids of this post

DECLARE c1 CURSOR LOCAL
FOR SELECT PostID FROM Posts
WHERE ParentID = @ParentID Order by PostSortKey ASC

OPEN c1

FETCH NEXT FROM c1 INTO @ParentID

WHILE @@FETCH_STATUS = 0

BEGIN
-- Build a key up as we go
IF @PostLevel = 1
SET @PostKey = convert(varchar, @Counter)
ELSE
SET @PostKey = @ParentPostKey + '.' + convert(varchar, @Counter)

-- Put this record in the temp table
INSERT #NestedPosts (PostID, PostKey, PostLevel)
VALUES (@ParentID, @PostKey, @PostLevel)

SET @NextLevel = @PostLevel + 1

-- Process all the children for this post
EXEC GetChildren @ParentID, @NextLevel, @PostKey

SET @Counter = @Counter + 1

-- And get the next record at this level

FETCH NEXT FROM c1 INTO @ParentID
END

CLOSE c1
DEALLOCATE c1
SET NOCOUNT OFF
END

===========================================================

*
This stored procedure runs through all the child posts for a given parent post and puts them into the temp table. It also builds up the hierarchical numbering scheme that was asked about in the question. The really tricky part is that it calls itself in order to handle any child records of the current record it's processing. You can have up to 32 levels of nesting in your stored procedure.

That means this little sample is limited to 32 levels of posts. I also could have used a variable called @@NESTLEVEL which SQL Server uses to track how far down a recursion tree you are. I built and manually maintained a variable called @PostLevel for the task.

The output from the SELECT statement above looks like this: *

SortID PostID ParentID PostLevel PostKey PostSortKey Subject
1 1 0 1 1 2000-12-06 20:54:29 First Post
2 3 1 2 1.1 2000-12-06 20:54:29 Child of First Post
3 5 3 3 1.1.1 2000-12-06 20:54:29 Child of First Child
4 7 5 4 1.1.1.1 2000-12-06 21:18:49 Smallest Kid
5 8 3 3 1.1.2 2000-12-06 21:28:22 Another Munchkin
6 6 1 2 1.2 2000-12-06 21:04:49 Another FP Child
7 2 0 1 2 2000-12-06 20:54:29 Second Post
8 4 2 2 2.1 2000-12-06 20:54:29 Child of 2nd Post


* That's about all there is to this. Try it and let me know what you think.