Configure Windows SharePoint Services Search Service Settings

By default, the search service is disabled in WSS 3.0. In order to enable it, goto your SharePoint Central Administration page and under the Operations tab, goto the “Services on the Server” list and click on WSS Search.

  • Service Account – You cannot use any built in accounts when specifiying the Service Account, however you can use local accounts. One account you can choose to use is the default Administrator account on your server, “myserver\Administrator”.
  • Content Access Account – Use an account that has full read access to your SharePoint Content Databases. As a security precaution, try not to use an account that has write permissions on the database. For example: “mydomain\sqlserveruser”.
  • Search Database – In most cases the default settings will work, change them as required.
  • Indexing Schedule – No new files will be searchable until they are indexed, depending on the load you wish to put on the server you can set this to be done as often as you want.

Upon submitting the form, the search service should start and begin indexing the content in your SharePoint site. Now would be a good time to install the Adobe IFilter to allow your search service be able to index PDF files, the earlier you install it the better, since it will only recognize PDF files added after the addon was installed.

Dynamically Creating HTML Elements Using Javascript

Suppose you want your users to submit a list of items through your web page. These items could be inputted through many means such as a text box, combobox, listbox, etc. There are many ghetto solutions you could use to implement this like comma delimited lists or multiple postbacks. There are however much more elegant, and suprisingly easier ways of doing this using javascript. As an example we will start with an input box with a link below it that allows you to spawn several more input boxes. In addition, each spawned input box comes with its own delete link, allowing the user to remove items if they choose to. Each input box is given a unique incremented ID that can be easily accessed later on through postback.

Here’s the javascript that makes it work:

<script type="text/javascript" language="javascript">     
    var software_number = 1;
    function addSoftwareInput() 
    {
        var d = document.createElement("div");
        var l = document.createElement("a");
        var software = document.createElement("input");
        software.setAttribute("type", "text");
        software.setAttribute("id", "software"+software_number);
        software.setAttribute("name", "software"+software_number);
        software.setAttribute("size", "50");
        software.setAttribute("maxlength", "74");
        l.setAttribute("href", "javascript:removeSoftwareInput('s"+software_number+"');");
        d.setAttribute("id", "s"+software_number); 
        
        var image = document.createTextNode("Delete");
        l.appendChild(image);
        
        
        d.appendChild(software);
        d.appendChild(l);
        
        document.getElementById("moreSoftware").appendChild(d);
        software_number++;
        software.focus();
    }
    
    function removeSoftwareInput(i) 
    { 
        var elm = document.getElementById(i); 
        document.getElementById("moreSoftware").removeChild(elm); 
    }
</script>

And the tiny amount of html to get it to show up:

<input id="ninjainput" type="hidden" name="ninjainput" />

Next, we’ll insert an additional javascript function that will populate our ninjainput when the user submits the page.

function populateStaticInput()
{
    var n = document.getElementById("ninjainput");
    var allsoftware = "";
    for( var i = 0; i < software_number; i++)
    {
        var currentele = document.getElementById("software"+i);
        if(currentele != null)
        {
            if(currentele.value.length > 0)
            {
                if(currentele.value.length > 74)
                    currentele.value = currentele.value.substring(0, 74);
                allsoftware = allsoftware + "~<>~" + currentele.value;
            }
        }
    }
    n.value = allsoftware;     
}

Notice that we are delimiting each item with a “~<>~”. Make sure to add this attribute to your form tag: onsubmit=”javascript:populateStaticInput();” This way the javascript will run and populate our ninjainput control before the page is sent to the server. Lastly, we will need a function (the example below is in C#) that will cut up the submitted list of items into a usable format, in this case an array of strings.

public string[] GetAllSoftwareInList(string rawsoftlist)
{
    string[] asoft = rawsoftlist.Split("~<>~".ToCharArray(), System.StringSplitOptions.RemoveEmptyEntries);
    return asoft;
}

And there you have it, the user doesn’t have to endure multiple postbacks or keep track of a comma delimited list. It’s presented in an organized and intuitive manner to the user and not too painful to implement for the developer.

Releasing Files In Use By Other Processes

When developing an application that uses a SQL Server Compact Edition database, you may run into a problem getting your application to build if you frequently compile it to test changes. Specifically the following error:

Problem generating manifest. The process cannot access the file ‘C:\…\mydb.sdf’ because it is being used by another process.

The problem is that your application didn’t properly release its lock on the SQLCE database file the last time you ran it. I find this especially happens when you’re debugging and hit an unhandled exception. Since your application runs as a child of the devenv.exe (Visual Studio) process, closing and reopening Visual Studio will release the lock on the SDF file and allow you to successfully compile again. Obviously, restarting Visual Studio everytime you want to test your application isn’t very convenient.

There is an easier solution to this problem. You’ll need to download Process Explorer, a free utility provided by Microsoft. According to the website, “Process Explorer shows you information about which handles and DLLs processes have opened or loaded”. This is precisely what we need to release the SDF file that Visual Studio has taken hostage.

So open up Process Explorer, and using the “Find Handle or DLL…” feature search for “sdf”. You may end up with several results, but what you’re looking for is the SDF that you use in your application. Once you find it, double click it. The file will then appear highlighted on the bottom half of the window, right click it and select “Close Handle”. The lock on the file will be destroyed, allowing you to successfully build your application without getting manifest generation errors.

Silently Getting Exception Details From Your Users in ASP.NET

Say you have an ASP web site that you’ve deployed and want to be notified when and why your website craps the bed. Since it’s live you probably have something like this in your web.config file:

<customErrors mode="RemoteOnly" defaultRedirect="error.aspx"/>

And while it hides all the exception details from your users, serving them a pretty error page, it has the side effect of returning the generic and completely useless System.Web.HttpUnhandledException. So the question is how do you, the developer, recieve the full stack trace and exception details when a user happens to come upon an error using your application? You might feel tempted to use the Application_Error event in Global.asax to catch the exception, parse out all the exception details and email them to yourself. Unfortunately that won’t work because ASP will still hide the stack trace from you. Instead of wasting your time writing code from scratch that will do this, you can accomplish the same thing by adding a few lines to your web.config file.

<system.net>
        <mailSettings>
            <smtp deliveryMethod="Network">
                <network host="yoursmtpserver"/>
            </smtp>
        </mailSettings>
    </system.net>
    <system.web>
        <healthMonitoring enabled="true" heartbeatInterval="0">
            <providers>
                <add name="ErrorEmailProvider" type="System.Web.Management.SimpleMailWebEventProvider" to="you@email.com" from="donotreply@you.com" buffer="false" subjectPrefix="[APP ERROR]"/>
            </providers>
            <rules>
                <add name="EmailErrors" eventName="All Errors" provider="ErrorEmailProvider" profile="Default" minInstances="1" maxLimit="Infinite" minInterval="00:01:00" custom=""/>
            </rules>
        </healthMonitoring>

With those blocks in your web.config file you will recieve an email containing all the information you need to track down which section of code failed. And at the same time your user recieves a friendly error page and is none the wiser. Here’s a sample of a simple IndexOutOfRangeException.

** Application Information **
---------------
Application domain: /LM/W3SVC/1/Root/HSR-1-128402415931345878
Trust level: Full
Application Virtual Path: /HSR
Application Path: C:\Inetpub\wwwroot\HSR\ Machine name: myserver


** Events **
---------------
Event code: 3005
Event message: An unhandled exception has occurred.
Event time: 11/22/2007 2:47:40 PM
Event time (UTC): 11/22/2007 9:47:40 PM
Event ID: 051ece02de474c8ea153eb0ffc058f25 Event sequence: 6 Event occurrence: 1 Event detail code: 0

Process information:
    Process ID: 4092
    Process name: w3wp.exe
    Account name: NT AUTHORITY\NETWORK SERVICE

Exception information:
    Exception type: System.IndexOutOfRangeException
    Exception message: Index 0 is either negative or above rows count.

Request information:
    Request URL: http://myserver/HSR/requeststatus.aspx?request=22
    Request path: /HSR/requeststatus.aspx
    User host address: 000.000.000.000
    User: someuser
    Is authenticated: True
    Authentication Type: Negotiate
    Thread account name: NT AUTHORITY\NETWORK SERVICE

Thread information:
    Thread ID: 1
    Thread account name: NT AUTHORITY\NETWORK SERVICE
    Is impersonating: False
    Stack trace:    at System.Data.DataView.GetElement(Int32 index)
   at System.Data.DataView.get_Item(Int32 recordIndex)
   at requeststatus.Page_Load(Object sender, EventArgs e) in c:\Inetpub\wwwroot\HSR\requeststatus.aspx.cs:line 27
   at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e)
   at System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e)
   at System.Web.UI.Control.OnLoad(EventArgs e)
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
5 of 5
12345