ZZzz's Microsoft Tech Notes

Take notes regarding to the Microsoft programming technologies that I used and learnt when working such as ASP.NET, SharePoint, WCF, WWF and etc. before I forgot. ^_^!

About Me

Tuesday 27 April 2010

Read the AD (Active Directory) from .NET

from sathya:.NET and Active Directory in c-sharpcorner.com (19/04/2007)

1. Two jargons:

GC - Global Catalog: a partial replica of all domains in the forest.

  1. The GC has a database table like structure which helps in faster searches.
  2. The GC contains directory data for all domains in a forest, but does not contain all the properties of each object. Instead, it contains only the properties specified for inclusion in GC.

LDAP - Lightweight directory access protocol: a full replica of a single domain.

A LDAP and GC path would look something like these:

  • "LDAP://<host name>/<object name>" 
  • "GC://<host name>/<object name>"

In the examples above, "LDAP:" specifies the LDAP provider. "GC:" uses the LDAP provider to bind to the Global Catalog service to execute fast queries.

2. Forming queries for AD

The LDAP search strings used to query AD is a little different from the normal SQL queries on DB. These queries are based on one or more of the key attributes as follows.

2.1 ObjectCategory

This could be 'user' or 'printer' or any defined category in the AD. If you would be searching only users then this value needs to be set to user e.g (objectCategory=user). By specifying this search narrows down, and you can expect to see results sooner. (like table in DB?)

2.2 AD attributes

There is a big list of fields that can be used in Activedirectory, apart from the extensive set it provides, ad administrators can add their own fields. The query can consist of any of the named fields of AD.e.g. (samAccountname=john.abraham). (like where condition in DB)

To combine the criteria, the normal bitwise operators (&, |, !) can be used,

For example, if I want to query for all the users whose distinguishedName begin with 'john' my query would look like this (&(objectCategory=user)(cn=john*))

If I wanted to find how many users whose names begin either with jack or jill this is how I would frame my query.
(&(objectCategory=user)(|(cn=jack*)(cn=jill*))).

Users whose mailed is empty.
(&(objectCategory=user)(!(mail=*)))

For more such examples you could refer

http://www.petri.co.il/ldap_search_samples_for_windows_2003_and_exchange.htm.
http://www.microsoft.com/technet/scriptcenter/guide/sas_ads_emwf.mspx?mfr=true

For specifying this search you need to be aware of the available properties/fields in your active directory.

2.3 AD date format

So much for queries, but then if you want to involve dates in your query, there is bit more of a job to be done, i.e. AD doesn't accept our normal date format for the queries, so the date needs to be converted into a AD readable format. Pls find below a function to do the same.

/// <summary>
/// Method to convert date to AD format
/// </summary>
/// <param name="date">date to convert</param>
/// <returns>string containing AD formatted date</returns>
private static string ToADDateString(DateTime date)
{
string year = date.Year.ToString();
int month = date.Month;
int day = date.Day;
int hr = date.Hour;
string sb = string.Empty;
sb += year;
if (month < 10)
{
sb += "0";
}
sb += month.ToString();
if (day < 10)
{
sb += "0";
}
sb += day.ToString();
if (hr < 10)
{
sb += "0";
}
sb += hr.ToString();
sb += "0000.0Z";
return sb.ToString();
}

2.4 Unique ID - objectGUID


Another challenge was to find a unique ID from AD which can be used as a primary key in cases when we need to take a snapshot of the AD to the database. You might wonder that the email id or aliasname of a person should be unique, but in certain organizations when  request for an extra mailbox or any service account is raised, the additional account is created in the same name as the existing one, thus making those attributes unusable as unique Ids.
In AD the unique id used is the objectGUID which cannot be directly inserted into the database as it's a 128 bit octet string. To store this into a Database it needs converted to a readable format for example a binary string. Find below a code snippet that would do just that.


byte[] arraybyte = (byte[])de.Properties["objectGUID"].Value;
StringBuilder OctetToHexStr = new StringBuilder();
for (int k = 0; k < arraybyte.Length; k++)
{
OctetToHexStr.Append(@"\" + Convert.ToString(Convert.ToByte(arraybyte[k]),16));
}


3. Accessing AD from .NET in 2 ways


3.1 ADO



objConnection = CreateObject("ADODB.Connection");
objConnection.Provider = "ADsDSOObject";
objConnection.Properties("User ID").Value = "myUser";
objConnection.Properties("Password").Value = "myPassword";
objConnection.Properties("Encrypt Password") = true;
objConnection.Open("Active Directory Provider");

ADODB.Command objCommand = new ADODB.Command();
objCommand.ActiveConnection = objConnection;
strBase = "<LDAP://OU=User Directory,DC=asia,DC=myDomain,DC=com>";

string strFilter ="(&(objectCategory=user)(objectClass=user)(whenCreated>=" + strFromDate +"))";
The following is sample code to do the same.
ADODB.Recordset rsAD = new ADODB.RecordsetClass();

try
{
rsAD.Open(strFilter,adConn,ADODB.CursorTypeEnum.adOpenForwardOnly,ADODB.LockTypeEnum.adLockReadOnly,0);
}
catch (Exception exp)
{
Response.Write(exp.Message);
Response.End();
}

DataTable userDataTable = new DataTable();
userDataTable.Columns.Add ("AccountName");
userDataTable.Columns.Add ("CommonName");
userDataTable.Columns.Add ("CreatedDate");
while(!rsAD.EOF)
{
DataRow newRow = userDataTable.NewRow();
newRow[0] = rsAD.Fields[0].Value;
newRow[1] = rsAD.Fields[1].Value;
newRow[2] = rsAD.Fields[2].Value;
userDataTable.Rows.Add(newRow);
rsAD.MoveNext();
}

3.2 System.DirectoryServices


using System;
using System.Collections;
using System.DirectoryServices;
using System.Data;
using System.Security.Permissions;
using System.IO;
using System.Text;
[assembly: SecurityPermission(SecurityAction.RequestMinimum, Unrestricted = true)]
namespace Web.Apps.ADInterface
{
/// <summary>
/// Class to interface with AD and search for new, modified and deleted users.
/// </summary>
public class ADSearch
{
#region Private Variables
private static string _gcPath = "GC://mydomain.com";
private static string _serviceAccountName = @"Europe\abcsdfs-S";
private static string _servicePassword = "2$%^&*()";
private DirectoryEntry entry = new DirectoryEntry();
#endregion
#region Constructor
private ADSearch()
{
entry.Path = _gcPath;
entry.Username = _serviceAccountName;
entry.Password = _servicePassword;
}
#endregion
#region Methods
/// <summary>
/// Method to Search for new,Modified and Deleted users
/// </summary>
/// <param name="createdDate"></param>
public static void SearchADUsers(DateTime createdDate, string path)
{
string strFilter = string.Empty;
string strFromDate = ToADDateString(Convert.ToDateTime(createdDate));


//Search criteria for fetching users whose account name, mail and distinguished name
are not empty and whose entries are changed since the specified date (either created
or modified after the specified date)



strFilter += "(&(objectCategory=user)(samAccountName=*)(mail=*)(distinguishedName=*)
(|(whenChanged>=" + strFromDate + ")(whenCreated>=" + strFromDate + ")))";


ADSearchUsers(strFilter, path);
}
/// <summary>
/// Method to Search for new,Modified and Deleted users
/// </summary>
public static void TakeADSnapshot()
{
string filter = string.Empty;
filter += "(&(objectCategory=user)(samAccountName=*)(mail=*)(distinguishedName=*))";
ADSnapshot(filter, @"C:\insert.CSV");
}


/// <summary>
/// Method to Search for new,Modified and Deleted users
/// </summary>
/// <param name="path">CSV file Path</param>
public static void TakeADSnapshot(string path)
{
string filter = string.Empty;
filter += "(&(objectCategory=user)(samAccountName=*)(mail=*)(distinguishedName=*))";
ADSnapshot(filter, path);
}
//The function below takes a snapshot of AD users who satisfy the specified criteria and
constructs a CSV file out of it, This is done it's the easiest way to move it into a database.
/// <summary>
/// Method to get take an AD snapshot
/// </summary>
/// <param name="filterString">AD Search string</param>
/// <param name="path">Path of CSV file</param>
private static void ADSearchUsers(string filterString, string path)
{


DirectoryEntry entry = new DirectoryEntry();
entry.Path = _gcPath;
entry.Username = _serviceAccountName;
entry.Password = _servicePassword;


DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = filterString.ToString();
TextWriter tw = new StreamWriter(path, true);
mySearcher.PageSize = 10;
mySearcher.CacheResults = false;
StringBuilder sqlinsert = null;


//Add all properties that need to be fetched
mySearcher.PropertiesToLoad.Add("displayName");
mySearcher.PropertiesToLoad.Add("givenname"); ;
mySearcher.PropertiesToLoad.Add("sn");
mySearcher.PropertiesToLoad.Add("ou");
mySearcher.PropertiesToLoad.Add("employeeType");
mySearcher.PropertiesToLoad.Add("mail");
mySearcher.PropertiesToLoad.Add("telephoneNumber");
mySearcher.PropertiesToLoad.Add("samAccountName");
mySearcher.PropertiesToLoad.Add("whenCreated");
mySearcher.PropertiesToLoad.Add("whenChanged");
mySearcher.PropertiesToLoad.Add("objectGUID");
mySearcher.PropertiesToLoad.Add("c");


//The search scope specifies how deep the search needs to be, it can be either
"base"- which means only in the current //level, and "OneLevel" which means the
base and one level below and then "subtree"-which means the entire tree needs //to be searched.


mySearcher.SearchScope = SearchScope.Subtree;
SearchResultCollection resultUsers = mySearcher.FindAll();
int fpos, spos;
string dn, newdn, newerdn;
foreach (SearchResult srUser in resultUsers)
{
try
{
DirectoryEntry de = srUser.GetDirectoryEntry();
byte[] arraybyte = (byte[])de.Properties["objectGUID"].Value;
StringBuilder OctetToHexStr = new StringBuilder();
for (int k = 0; k < arraybyte.Length; k++)
{
OctetToHexStr.Append(@"\" + Convert.ToString(Convert.ToByte(arraybyte[k]), 16));
}


dn = de.Properties["distinguishedName"][0].ToString();
sqlinsert = new StringBuilder();
//To get the domain name from Distinguished name
fpos = dn.IndexOf("DC=", 0);
newdn = dn.Substring(fpos, dn.Length - fpos);
spos = newdn.IndexOf(",DC=", 3);
newdn = newdn.Substring(0, spos);
newerdn = newdn.Substring("DC=".Length, newdn.Length - 3);
sqlinsert.Append(OctetToHexStr.ToString());
sqlinsert.Append(";");
sqlinsert.Append(de.Properties["givenname"].Value);
sqlinsert.Append(";");
sqlinsert.Append(de.Properties["sn"].Value);
sqlinsert.Append(";");
sqlinsert.Append(de.Properties["ou"].Value);
sqlinsert.Append(";");
sqlinsert.Append(de.Properties["employeeType"].Value);
sqlinsert.Append(";");


sqlinsert.Append(";");
sqlinsert.Append(de.Properties["mail"].Value);
sqlinsert.Append(";");
sqlinsert.Append(de.Properties["samAccountName"].Value);
sqlinsert.Append(";");
sqlinsert.Append(de.Properties["c"].Value);
sqlinsert.Append(";");
sqlinsert.Append(de.Properties["l"].Value);
sqlinsert.Append(";");
sqlinsert.Append(Convert.ToDateTime(de.Properties["whenChanged"][0].ToString().TrimEnd()).ToString
("dd-MMM-yyyy"));
sqlinsert.Append(";");
sqlinsert.Append(Convert.ToDateTime(de.Properties["whenCreated"][0].ToString().TrimEnd()).ToString
("dd-MMM-yyyy"));
sqlinsert.Append(";");
&nbsp; sqlinsert.Append(DateTime.Now.ToString("dd-MMM-yyyy"));
sqlinsert.Append(";");
sqlinsert.Append(newerdn);
sqlinsert.Append(";");
sqlinsert.Append(de.Properties["legacyExchangeDN"].Value);
sqlinsert.Append(";");
sqlinsert.Append(de.Properties["distinguishedName"].Value);
//sqlinsert = OctetToHexStr+ ";" + de.Properties["givenname"].Value + ";" +
de.Properties["sn"].Value + ";" + de.Properties["ou"].Value+";" + de.Properties["employeeType"].Value +
";" + +";"+de.Properties["mail"].Value+";"+de.Properties["samAccountName"].Value+";"+ de.Properties
["c"].Value+";"+de.Properties["l"].Value+";"+Convert.ToDateTime(de.Properties["whenChanged"]
[0].ToString().TrimEnd()).ToString("dd-MMM-yyyy")+";"+Convert.ToDateTime(de.Properties["whenCreated"]
[0].ToString().TrimEnd()).ToString("dd-MMM-yyyy")+";"+DateTime.Now.ToString("dd-MMM-yyyy") +";"
+newerdn+";"+de.Properties["legacyExchangeDN"].Value+ ";"+de.Properties["distinguishedName"].Value;
de.Close();
tw.WriteLine(sqlinsert);
sqlinsert.Remove(0, sqlinsert.Length);

}
catch
{
throw;
}
}
tw.Close();
}

#endregion
}
}

4. Conclusion



  • Active directory searches would be pretty slow compared to database searches, so it's imperative to narrow down the search criteria as much as possible.
  • To search for deleted users in Active directory could be quite a challenge as the deleted items are physically moved to the obsolete users directory and after a certain "tombstone" period will be permanently deleted. But organisations follow some pattern of identifying deleted user's by certain means like suffixing the samAccountname with-Deleted or prefixing the username with a "_" and so on. Before you do a search on deleted users, it would be worthwhile to consult your AD administrator to know the convention followed.
  • There are much more properties attached to the directorysearcher and directoryentry classes, it would be worthwhile to go through them in msdn. The class DirectorySearcher would give only a readonly snapshot of AD, to do modifications on the AD you would have to follow different pattern.

5. Credit


sathya:.NET and Active Directory in c-sharpcorner.com

Running 32-bit assemblies in an ASP.NET application on a 64-bit machine

Found it online, it must be useful one day...

If you are trying to run our 32 bit builds in an ASP.NET application on a 64-bit machine, you need to add this to your web.config

C# (.NET 2.0)

<system.codedom> 
<compilers>
<compiler
language="c#;cs;csharp" extension=".cs"
compilerOptions="/platform:x86"
type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</compilers>
</system.codedom>


VB.NET (.NET 2.0)


<system.codedom> 
<compilers>
<compiler
language="vb;vbs;visualbasic;vbscript" extension=".vb"
compilerOptions="/platform:x86"
type="Microsoft.VisualBasic.VBCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</compilers>
</system.codedom>

The important part is: compilerOptions="/platform:x86"

Which you could also set with a page directive


<%@ Page Language="C#" ... CompilerOptions="/platform:x86" %>

And you may also have to set IIS into 32 bit mode.


Reference:


http://support.microsoft.com/kb/894435/en-us

http://www.atalasoft.com/kb/article.aspx?id=10181

Could not load file or assembly ... An attempt was made to load a program with an incorrect format.

1. ERROR

I got the error below a few day ago after adding a very very normal class into an existing assembly.

"Could not load file or assembly 'R...tools, Version=1.0.0.0, Culture=neutral, PublicKeyToken=21ef81843f5d77b6' or one of its dependencies. An attempt was made to load a program with an incorrect format."

or

"Could not load file or assembly 'R...tools, Version=1.0.0.0, Culture=neutral, PublicKeyToken=21ef81843f5d77b6' or one of its dependencies."

The class I added:

public static class Generic
{
static Generic ()
{
......
}

public static string ClearValue (string value)
{
......
return value;
}
}

Before this I’ve tested other functions and they worked perfect, but I got the same error even if I deleted the new above class. I had no idea what's going on at all and it seemed suddenly came out.

After a lot of search and reading several articles online, it seemed the 64bit issue again. Again? Yes, again, I've already met a lot of 64bit problems when working on SharePoint on Win2K8 Server.

2. REASON


Actually before adding the above new class, I created another class for reading excel file using @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + fullFilePathAndName + @";Extended Properties=""Excel 12.0;HDR=YES;""" and it works fine only if the application that invoked the excel reader class is complied with x86 platform target. Otherwise you would get the error :

System.InvalidOperationException: The 'Microsoft.ACE.OLEDB.12.0' provider is not registered on the local machine.

So I changed both the assembly and the main application's Platform Target from 'Any CPU' to 'x86' in the Build tab of the project properties to make it work and it did, which was wrong: only the main application's PlatForm Target should be changed to ‘x86’ not the assembly.

The second mistake is more unforgivable: I didn’t retest all other functions that might only work in x64 environment.

3. WORKAROUND


I just changed the Platform Target of the assembly back to 'Any CPU' (Sometimes I have to change it to 'x64' and back to 'Any CPU' and then it takes effect.). They work perfect as before as long as the main application doesn't invoke the x64-only function.

So what if another main application that need to invoke the all functions in the assembly including the x86-only one as well as the x64-only one? Maybe I have to access excel in another way.

4. OTHER SOLUTIONS


There are several other ways I found online but I didn't try them. They might help one day if the similar error comes out again...


  1. Use ProcMon.exe from Microsoft (free tool) to find out which and where the assemblies are being loaded from.

  2. Set IIS in compatibility mode for 32 bits.


(1)Open a command prompt and navigate to the “%systemdrive%\Inetpub\AdminScripts” directory:


cd %systemdrive%\Inetpub\AdminScripts


(2)And then type the following command:


cscript.exe adsutil.vbs set W3SVC/AppPools/Enable32BitAppOnWin64 1



(3)Press ENTER.


Reference: http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/13f991a5-45eb-496c-8618-2179c3753bb0.mspx?mfr=true

Monday 26 April 2010

Using Ajax in SharePoint Web Parts

Applicable for .NET 3.5

1. web.config

a.

Make a copy of the web.config file in C:\inetpub\wwwroot\wss\VirtualDirectories\80, so that you can restore it in the event of errors in the next few steps.

b.

Before </configSections>,copy and paste the following:

    <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication" />
<sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="Everywhere" />
<section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication" />
<section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication" />
</sectionGroup>
</sectionGroup>
</sectionGroup>

C.


Before </SafeControls>, insert the following:


<SafeControl Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Web.UI" TypeName="*" Safe="True" />

d.


Before </httpHandlers>, insert the following:


<remove verb="*" path="*.asmx" />
<add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false" />

e.


Add the following parts just before </httpModules>.


<add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

f.


Insert the following between <compilation batch="false" debug="true"><assemblies> and </assemblies><expressionBuilders>


<add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

g.


Before </configuration>, insert the following:


  <system.web.extensions>
<scripting>
<webServices>
<!-- Uncomment this line to customize maxJsonLength and add a custom converter -->
<!--
<jsonSerialization maxJsonLength="500">
<converters>
<add name="ConvertMe" type="Acme.SubAcme.ConvertMeTypeConverter"/>
</converters>
</jsonSerialization>
-->
<!-- Uncomment this line to enable the authentication service. Include requireSSL="true" if appropriate. -->
<!--
<authenticationService enabled="true" requireSSL = "truefalse"/>
-->
<!-- Uncomment these lines to enable the profile service. To allow profile properties to be retrieved
and modified in ASP.NET AJAX applications, you need to add each property name to the readAccessProperties and
writeAccessProperties attributes. -->
<!--
<profileService enabled="true"
readAccessProperties="propertyname1,propertyname2"
writeAccessProperties="propertyname1,propertyname2" />
-->
</webServices>
<!--
<scriptResourceHandler enableCompression="true" enableCaching="true" />
-->
</scripting>
</system.web.extensions>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules>
<add name="ScriptModule" preCondition="integratedMode" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</modules>
<handlers>
<remove name="WebServiceHandlerFactory-Integrated" />
<add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</handlers>
</system.webServer>

h. Add the following before


2. Test web.config


Save and close the modified web.config file. Close the editor (VS.NET). And then to test that the web.config isn’t completely broken, open the IE and browse to the SharePoint again. Nothing should have changed.


3. In Web Parts


If using the user control (.ascx) in the web parts, copy the .ascx file and related code behind into “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\LAYOUTS\” and in the override member function CreateChildControls() of web parts:


Control myUserControl = this.Page.LoadControl("\\_layouts\\UserControlForm.ascx");
this.Controls.Add(myUserControl);

4. In .ascx file


Insert the following between </style> and <table,


<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>

and add the following at the end


    </ContentTemplate>
</asp:UpdatePanel>

Actually ScriptManager could be anywhere.



I tried this in CreateChildControl():


if (ScriptManager.GetCurrent(this.Page) == null)
{
ScriptManager sm1 = new ScriptManager();
sm1.ID = "sm1";
this.Controls.Add(sm1);
}

Control myUserControl = this.Page.LoadControl("\\_layouts\\UserControlForm.ascx");

UpdatePanel upd1 = new UpdatePanel();
upd1.UpdateMode = UpdatePanelUpdateMode.Conditional;
upd1.ContentTemplateContainer.Controls.Add(myUserControl);
this.Controls.Add(upd1);

but it didn’t work and I don’t know why.

Followers