ASP.NET
Today, I was struggling with adding and removing entries from web.config/appsettings in my feature receiver class. Well, it was quite easy to add , but removing was giving me a real hard time. I used my perfect indexing tool and I came across these great posts from Daniel Larson and Tony Bierman. By first look at their posts I realized that removing requires a *right* call to SPWebConfigModification constructor, otherwise it won't ever happen. Both posts are for adding Ajax http handlers, but one can easily alter them to make the solution work for appsettings as well.
protected void ModifyWebApplication(SPWebApplication app, bool removeModification)
{
SPWebConfigModification modification = new SPWebConfigModification("add[@key='TotalDigits']", "configuration/appSettings");
modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode ;
modification.Value = "<add key=\"TotalDigits\" value=\"21\" />";
modification.Sequence = 0;
if (removeModification)
app.WebConfigModifications.Remove(modification);
else
app.WebConfigModifications.Add(modification);
SPFarm.Local.Services.GetValue().ApplyWebConfigModifications();
}
As Daniel has mentioned in his comments, a real benefit of using the SPWebApplication in your code is that the changes are applied to the farm, since the SPWebApplication represents that virtual web application that lives in the farm context.Thanks Daniel and Tony!
SPWebPartCollection and IsIncludedFilter are both obsolete in V3, so I will use other new methods and properties to set the audience targeting property to a SharePoint Site Group which is part of “All Site Users” audience by default.
SPWeb web = new SPSite("http://server/mysite").OpenWeb();
SPLimitedWebPartManager wm = web.GetLimitedWebPartManager("default.aspx", System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);
foreach (WebPart wp in webParts)
{
SiteUserManager sum = wp as SiteUserManager;
if (sum != null && string.CompareOrdinal(sum.Title, wp.Title) == 0)
{
sum.AuthorizationFilter = ";;;;" + web.AssociatedOwnerGroup.Name ;
wm.SaveChanges(sum);
break;
}
}
AuthorizationFilter is an arbitrary string to determine whether a WebPart control is authorized to be added to a page or not. You either have to give it a valid GUID of your audience or like what I did above provide the name of the group prefixed by “;;;;”.
In order to bind data returned from database or a search query to a datalist at runtime in your web part , there are certain steps that you should take,otherwise data list won't render its content.The most important step is setting the ItemTemplate property to a proper template:
Web Part:
protected DataList _dtList;
protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
{
EnsureChildControls();
// Database connection and populating a datatable called prDT for example
this._dtList.ItemTemplate = new ProductTemplate();
this._dtList.DataSource = prDT;
this._dtList.DataBind();
this._dtList.RenderControl(writer);
}
protected override void CreateChildControls()
{
this._dtList = new DataList();
this._dtList.RepeatDirection = RepeatDirection.Horizontal;
this._dtList.RepeatColumns = 2;
Controls.Add(this._dtList);
}
ProductTemplate.cs:
public class ProductTemplate : ITemplate
{
void ITemplate.InstantiateIn(Control container)
{
Label itemlbl = new Label();
itemlbl.Width = 110;
itemlbl.DataBinding += new EventHandler(itemlbl_DataBinding);
container.Controls.Add(itemlbl);
}
void itemlbl_DataBinding(object sender, EventArgs e)
{
Label lbldata = (Label)sender;
DataListItem container = (DataListItem)lbldata.NamingContainer;
lbldata.Text= Convert.ToString(DataBinder.Eval(((DataListItem)container).DataItem,"ProductName"))
}
}
This post applies to SharePoint or any ASP.NET applications whose authentication mechanism is NTLM and impersonation is enabled. SharePoint,by default,uses impersonation which results in an impersonated token, which is not primary token on the SPS Server.When HTTP request leaves machine boundaries, the impersonated token becomes the root of all evil.Let's have a deeper look by running the code below in your web part/web part page or generally speaking in any http context:
1. HttpWebRequest userReq = (HttpWebRequest)WebRequest.Create("http://mysite/sharedDoc/test.xml");
2. userReq.AllowAutoRedirect=false;
3. userReq.Credentials = System.Net.CredentialCache.DefaultCredentials;
4. HttpWebResponse serverResponse = (HttpWebResponse) userReq.GetResponse();
It works fine on your development enviroment (if everything is on the same server),but as soon as you go live on production where IIS and SQL Server are located in two differnet machines , you will get : The remote server returned an error: (401) Unauthorized
Use the code below instead and everything just works fine!!!!
1. HttpWebRequest userReq = (HttpWebRequest)WebRequest.Create(fullLink);
2. userReq.AllowAutoRedirect=false;
3. userReq.Credentials = new System.Net.NetworkCredential("Bob","pass@word","Domain");
4. HttpWebResponse serverResponse = (HttpWebResponse) userReq.GetResponse();
Here is the brief explanation of what's happening when you receive the error for the first snippet.
I ) Bob authenticates with web server via browser using NTLM ,so no password is sent over the wire and only a windows security token will be created
II) IIS creates a windows security token for poor Bob.This token is an impersonation token and not a primary token (because impersonation is enabled). It has also no network credentials and cannot be delegated to any remote servers (because NTLM is used)
III)ASP.NET code accesses DefaultCredentials to use in WebRequest. DefaultCredentials are based on impersonation token,but DefaultCredentials contains an impersonated token and not the primary one, so it cannot hop to SharePoint Content Database (where the document and document library is stored) to create the web request.
Main reason that the second snippet works is the fact that you are re-creating the windows security context and providing the password. To solve issues of this type you have couple of solutions :
Solution 1)As mentioned above use direct creation of the security context (store credentials in a safe palce)
Solution 2)Use Kerberos along with Delegation of both Bob's credential and the machine which hosts SPS (IIS Server).These setting must be done in AD , so refer to this excellent article : http://support.microsoft.com/kb/810572/
Solution3)Use Basic Authentication to send users credential in plaintext (Make sure you secure the transportation layer though using HTTPS,etc)
1) string ProcessIdentity = WindowsIdentity.GetCurrent().Name;
- It has nothing to do with ASP.NET. In fact this is the lowest level of windows security programming that you can do. It basically tells you who is this thread running as?
- No matter which authentication mechanism you use in your ASP.NET applications (Windows Integrated,Forms,Basic,Digest and etc), whether you impersonate or you use anonymous access, at the end of the day they all resolve in a windows account. The above statement returns the name of that account.
- The windows account which is returned is a process identity (App pool identity, Machine identity, etc) OR impersonated user (if impersonation is used) OR anonymous access identity (if anonymous access is enabled). Bear in mind that the security context which is the result of enabling the anonymous access is ONLY used when your web parts interact with local or network resources and SharePoint is still using Process Identity to call content or configurations database, regardless of whether web application is set to use Form or Windows Integrated Authentication.
2) string ASPNetUser = Context.User.Identity.Name;
This is an ASP.NET technique to get the owner of the HTTP context. Based on authentication type, impersonation settings it might be different from one environment to another one.
3) string WssUser = SPContext.Current.Web.CurrentUser.Name;
AS mentioned din my previous blog post , this is a new class in SharePoint 2007 to extract the WSS user which gives u a more friendly name than the ASP.NET user.
Fortunately Windows SharePoint Services V3 (SharePoint 2007) Object model offers a high level of backward compatibility with its predecessor(V2) , so we expect that most of our code written for previous version just work fine in new version as well. Microsoft has introduced a new class in Windows SharePoint Services V3 (SharePoint 2007) called SPContext. I have seen that some developers are having difficulty understanding the concept of CONTEXT in web applications particularly in SharePoint. SPContext in WSS V3 is not a new concept. It basically does what we used to do using SPControl in WSS V2, except it is a cleaner way of getting an entry point to a site or site collection in V3.
WSS V2:
SPSite siteCollect= SPControl.GetContextSite(this.context); //returns the site collection in which current site exists
SPWeb site= SPControl.GetContextWeb(this.context); //returns the site in which your code is executing
WSS V3:
SPSite siteCollect= SPControl.GetContextSite(this.context); //returns the site collection in which current site exists
SPSite siteCollect= SPContext.Current.Site //returns the site collection in which current context exists
SPWeb site= SPControl.GetContextWeb(this.context); //returns the site in which your code is executing
SPWeb site= SPContext.Current.Web; //returns the site in which current context exists
One important thing to remember is that all above statements work only if your code is running in the context of a SharePoint site. If your code is running in another IIS web site different than the IIS web site in which SharePoint site exists, none of the above statement works properly and you have to use the following piece of code in both WSS V2 and V3:
SPSite sc2= new SPSite("http://localhost"); //returns the site collection exists in localhost virtual server(IIS WebSite)
As you see, here we have to explicitly state which context we are working with since we are outside of the SharePoint site context.
I have been repeatedly asked about how to prevent users from clicking submit button more than once. If you have ever faced such a problem, you would probably know that there are many ways suggested by folks to handle this, things like using two buttons, disabling submit button using JavaScript and so on. Regrettably I never did find anything that worked like they said it would and more importantly some of those methods causes your server side events not to fire. The way I deal with this issue is not just disabling the button by greying it out, but not letting the form to be posted back using the code below:
1) Put this in between the HEAD tags of your page:
<HEAD>
...
<SCRIPT language=javascript>
var isSubmittedFlag= false;
</SCRIPT>
...
</HEAD>
2) Add the following java script code to the button attributes collection in Page_Load() event of your page
btnSubmit.Attributes.Add("onclick", "BLOCKED SCRIPTif(isSubmittedFlag){alert('Your first request is still being processed');return false;}else{isSubmittedFlag=true;return true;}")
After reading this document:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odc_SP2003_ta/html/ODC_WritingCustomWebServicesforSPPT.asp
I implemented a web service to be used by my sharepoint users. the first thing came to my mind was how to apply my own custom authorization to this web service, so it could be called by only an specific user and denied for all other users. I went ahead and changed the web.config of ISAPI folder to load ASP.NET URLAuthorization http module (by default Sharepoint removes urlAuthorization module from web.config of wwwroot):
<location allowOverride="false">
<system.web>
<httpModules>
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule"/>
</httpModules>
</system.web>
</location>
I created the folder "MyCustomWebService" in ISAPI folder and moved all of my web service related files to that folder. I created a web.config file in the new folder and added the following lines to it:
<authorization>
<allow users="win2003-dv\administrator" />
<deny users="win2003-dv\sp_reader" />
</authorization>
Now when I call my web service (uploadfile.asmx) through Internet Explorer (http://portal/_vti_bin/MyCustomWebService/uploadfile.asmx), I will receive access denied for sp_reader and not for administrator (as I expected)
As an ASP.NET developer, what really bugs me the most is the security issues related to current web context, in which you do your coding .I have been recently working on an ASP.NET project, which requires me to frequently query Active Directory to obtain necessary information about users, groups and so on. I mostly use System.DirectoryServices and its two famous classes DirectoryEntry and DirectorySearcher. I guess the biggest challenge working with Active Directory from a web context is that fact that AD requires a primary token all the time. As long as IIS server has a user name and password (not just a hash of the password as the result of NTLM authentication) and can hand it over to AD you are fine, otherwise you are toast and soon you will end up receiving various nasty messages from AD. If I can convince my clients to pass credentials to System.DirectoryServices code using the DirectoryEntry class constructor or by using the Username and Password properties, then this method is my preferred one. However you should consider securing your credentials and not leave them in clear text anywhere in your app. For the sake of demonstration, let’s assume that you have written a piece of code to authenticate to AD to do some work:
DirectoryEntry adSharepointUsers=null;
try
{
adSharepointUsers = new DirectoryEntry("LDAP://mydomain","ADUser","password");
........
}
catch(Exception ex)
{
throw ex;
}
Everything works fine on your development machine, but once you have deployed your app to the production ,you will be trapped by "The authentication mechanism is unknown" error. If that's the case you might try passing username with the domain name at the same time
ie: MyDomain/ADUser.
DirectoryEntry adSharepointUsers=null;
try
{
adSharepointUsers = new DirectoryEntry("LDAP://MyDomain","MyDomain/ADUser","password");
........
}
catch(Exception ex)
{
throw ex;
}
Yes, it does the trick!
Have you ever tried to use "Page Viewer Webpart" in sharepoint ? There is a big problem setting the height for this webpart when the page, that is loaded in its iframe, varies in height. In some cases scrollbars appear even if the height is set in webpart property. In respond to a client request, I have recently developed a WSS template that contains a web part which iterates through a pre-defined/pre-populated document library, looking for default.aspx or default.html and renders that page in an IFRAME. I had to dynamically resize the document library in run time according to the height of the page it points to. My solution contains a java script file named "iFrameResizer.js" that is set to be "Content" of my web part and a little bit of code in my web part to link to this script file to my web part.
//-----------------------------------------------------------------
//iFrameResizer.js
//-----------------------------------------------------------------
/* This function is used to adjust the iframe height to its content */
function iResizer(iframeID,iframeName)
{
moz=document.getElementById&&!document.all
mozHeightOffset=20
document.getElementById(iframeID).width="100%"
document.getElementById(iframeID).height=window.frames[iframeName].document.body.scrollHeight+(moz?mozHeightOffset:0)
}
Then in the constructor of web part I added an event handler to the PreRender event that loads “iFrameResizer.js” onto the page on prerendering :
using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebPartPages;
namespace DevHorizon.Sharepoint.iFrameWP
{
///
/// Description for Frame.
///
[DefaultProperty("Url"),
ToolboxData("<{0}:Frame runat=server>"),
XmlRoot(Namespace="DevHorizon.Sharepoint.iFrameWP")]
public class Frame : WebPart
{
private const string defaultUrl = "shared%20documents/default.htm";
private string url = defaultUrl;
// Used for the linked script file
private const string iFrameResizerFileName = "iFrameResizer.js";
private const string iFrameResizerIncludeScriptKey = "iFrameResizerIncludeScript";
private const string IncludeScriptFormat =@" language=""{0}"" src=""{1}{2}"">";
[Browsable(true),Category("Miscellaneous"),DefaultValue(defaultUrl),WebPartStorage(Storage.Personal),FriendlyName("Url"),Description("URL Property")]
public string Url
{
get
{
return url;
}
set
{
url = value;
}
}
// Contructor
public Frame()
{
this.PreRender += new EventHandler(WebPart_Frame_PreRender);
}
//Function which will register the linked file script
private void WebPart_Frame_PreRender(object sender , System.EventArgs e )
{
string location = null;
// Make sure that the script was not already added to the page.
if (!Page.IsClientScriptBlockRegistered(iFrameResizerIncludeScriptKey))
{
location = this.ClassResourcePath + "/";
// Create the client script block.
string includeScript = String.Format(IncludeScriptFormat, "javascript", location, iFrameResizerFileName);
Page.RegisterClientScriptBlock(iFrameResizerIncludeScriptKey, includeScript);
}
}
protected override void CreateChildControls()
{
Literal lit = new Literal();
Guid gid=Guid.NewGuid();
string iframeID="ExxoniFrameWPId" + gid.ToString();
string iframeName= "ExxoniFrameWPName" + gid.ToString();
lit.Text = "";
this.Controls.Add(lit);
}
protected override void RenderWebPart(HtmlTextWriter output)
{
try
{
RenderChildren(output);
}
//display diagnostic error information if exception occurs
catch(Exception ex)
{
output.Write("
");
output.Write("Exception Type: " + ex.GetType().ToString() + "
");
output.Write("Exception Message: " + ex.Message + "
" );
output.Write("Stack Trace:" + ex.StackTrace.Replace("\n", "
"));
output.Write("
");
}
}
}
}
There is one thing to remember:
I assume that iFrameResizer.js has been already added to the solution as "content" and is deployed to wpresources (or _wpresources if web part is installed in GAC). You have to add the following code to Manifest.xml
Yesterday, I was just trying to attach a DIME attachment to my soap message and send it to a WSE 2.0 enabled web service resides on my virtual server (VMWare). I was getting “Message Expired” or “An error was discovered processing the header” all the time. It was obvious that this was happening on the client since soap request was not reaching to the server (I was monitoring the server by MSSOAPTrace and nothing was there). After I did a lot of changes to the client I came to realize that Date and Time on the server was out of synch with client. That was all about it.
Various places (including http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/gngrfIdentitySection.asp), say:
You can create the encrypted credentials and store them in the registry with the ASP.NET Set Registry console application (Aspnet_setreg.exe), which uses CryptProtectData to accomplish the encryption. To download Aspnet_setreg.exe, along with the Visual C++ source code and documentation, visit the Web site www.asp.net and search for "aspnet_setreg".I have been unable to locate the source code for this program. The best I could find on www.asp.net was a forum thread by someone asking where the source code is, and none having an answer.)
So, where is the source code?! I am looking to duplicate its functionality in my program.
Michael Schwarz has built an amazing library (AJAX) to allow server-side processing without requiring postback.In fact his framework exposes a functionality to asynchronously sending and receiving HTTP Requests and HTTP Responses to and from the server. I guess Google suggest and Google map are both using the same technique. I'm sure that AJAX will change the asynchronous design patterns and will have a great impact on separating tiers in application architecture soon. Here is the definition of AJAX I found in Wikipedia.

A web.config of an ASP.NET application has a compilation element which exposes a Debug attribute that can be set to either "True" to produce release(retail) binaries or "False" for debug binaries.In both cases what is ultimately generated is binary ,but the way they are generated and the way they affect general performance of your ASP.NET application is very different.
- In most of the cases I don't really need each page of my ASP.NET application to be compiled individually into separate assemblies (Debug="True"). I know the cost of loading each assembly into memory and how it kills the performance. I would even delete global.asax for small ASP.NET applications where I don't use it at all as it is also compiled into a separate assembly.
- I do really need the pages under each folder to be batch-compiled by arriving of the first request rather than multiple compilations upon requests to different pages. Be informed that batch compilation occurs at the directory level, not the application level and there is no batch-compilation when Debug="True".
- Many people bring up some lame excuses that our web server is behind the firewall and who cares about the security!!!! Well, that’s ludicrous that people never think about the problem ahead till they are in trouble. I’d say driving a hummer doesn’t mean that you shouldn't buckle up, Firewall is cool but it doesn’t mean that you as a developer should forget about the security principles that you must consider in the project's life cycle. When Debug="True" is specified debug symbol file, compiler command line file, compiler output file, etc are all compiled and placed in the "Windows\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET" in addition to the assembly. It means your source code is deployed to the production server and can easily be deciphered.Beside that the inclusion of debug information ALSO reduces performance. (but allows a debugger to be attached to step through the assembly's code, and also allows ASP.NET to provide additional information when an exception is thrown such as the line on which the exception was thrown)
Microsoft offers an excellent whitepaper which I really suggest you to read before deploying your application to the production server.