Have you ever tried implementing Live ID Authentication into your asp.net web application? Well, it is not same as old Passport Authentication. From IIS 7 onwards we do not have option to implement Passport authentication. In place of it we have a Live ID SDK to implement Live ID Authentication in your public facing Web Site. You can register your site/URL to use Live ID from here:
http://msdn.microsoft.com/en-us/library/bb676626.aspx
Also you can try a sample code in your test server AKA localserver:
http://msdn.microsoft.com/en-us/library/bb676621.aspx
One of our partners proposed a unique solution and wanted me to develop a prototype for it. They have a public facing Web Site with multiple sub folders (virtual site) for different types of content. They implemented Windows Based authentication in such a way, so that different groups of users have permissions to browse different sub folders. Now they wanted to implement Live ID Authentication on this Web Application in such a way, so that each user can be mapped with a specific Live ID.
When a user logs-in for the first time he/she will be redirected to Live Site for authentication and then re-directed to the site and needs to insert their Domain login-id for the first time. Then, for consecutive usage, they will only enter with Live ID.
I implemented this by using HTTP Module and SQL Server additionally. The flow of entire application and the code is attached below. It is highly recommended to go through the above mentioned sample code first and try implementing it before implementing the proposed solution. Also get an Application ID for your Public Facing Web Site. One significant change, in sample, the webauth-handler.aspx remains under the /webauth/sample/ folder. Hence the default return URL is http://localhost/webauth/sample/webauth-handler.aspx. But in our case, it is http://www.contoso.com/webauth-handler.aspx i.e, in the root folder of the site. We also added the registration.aspx at the root folder. The SQL table Create statement is as below:
CREATE TABLE [dbo].[AuthTable](
[adusername] [varchar](50) NULL,
[usertoken] [varchar](50) NULL
) ON [PRIMARY]
Demo download URL: http://blogs.msdn.com/pranab/attachment/9889567.ashx
We can achieve this by creating an ItemAdded event receiver. To extract the metadata values from Excel cells, we need OpenXML. In my case, I used a solution called ExcelPackage from CodePlex (http://www.codeplex.com/ExcelPackage/). I modified the source code of ExcelPackage a little. The reason is ExcelPackage expects the source excel file located in a physical file path. But in our case the files will be in a Document Library or in SP content DB. So I used filestream instead of filepath.
I modified the ExcelPackage.cs file and added this new constructor:
public ExcelPackage(Stream fileContent)
{
_outputFolderPath = "";
_package = Package.Open(fileContent, FileMode.Open, FileAccess.ReadWrite);
}
I added my modified ExcelPackage.dll in my own project. I have used a event receiver and feature receiver combination. In the event receiver I used this within ItemAdded:
public override void ItemAdded(SPItemEventProperties properties)
{
MemoryStream fileStream = new MemoryStream();
fileStream.Write(properties.ListItem.File.OpenBinary(), 0, Convert.ToInt32(properties.ListItem.File.TotalLength));
using (ExcelPackage xlPackage = new ExcelPackage(fileStream))
{
ExcelWorksheet worksheet = xlPackage.Workbook.Worksheets[1];
properties.ListItem["Amount"] = worksheet.Cell(3, 5).Value;
properties.ListItem["Rate"] = worksheet.Cell(5, 5).Value;
properties.ListItem["Time"] = worksheet.Cell(7, 5).Value;
properties.ListItem["Monthly Payment"] = worksheet.Cell(10, 5).Value;
properties.ListItem["Client Name"] = worksheet.Cell(2, 3).Value;
string assignDate = worksheet.Cell(4, 3).Value;
properties.ListItem["Contract Date"] = DateTime.Parse(assignDate);
}
fileStream.Close();
properties.ListItem.SystemUpdate();
}
Now I created a document Library with MS Excel as Document Template and with additional fields like Amount, Rate, Time etc. I also created an Excel Workbook with this structure and where Monthly Payment has a function to calculate the payment for our mortgage calculation.
Once done, I published this excel file as the template file of our document library. I also implemented our event receiver and feature receiver combo for this document Library. If required, you need to add the ExcelPackage.dll into GAC. Now add a new file in this Document Library and you will see the values from respective cell values will appear in relevant metadata columns.
Usage report in MOSS 2007 gives you somewhat fare information about site usage. But if you need customized report to satisfy your specific requirement, then there is hardly any option to extend or customize those reports. For example I want usage reports should show following information for a specific web application:
1. Total and average hits per hour
2. Total and average hits from various location (IP)
3. Total and average hits per url
In general usage reports use these tables from <SharedServicesName>_DB (in my case it is SharedServices1_DB):
dbo.ANLHit, dbo.ANLHistoricalWebUsage, dbo.ANLHistoricalSiteUsage etc.
But it’s a kind of taboo to use the underlying db tables of SharePoint. But we can create our own application to store the usage history in our own db table(s) and expose those data using SSRS reports through MOSS dashboard.
Step1
Create your own database in SQL Server and add following table and views:
CREATE TABLE [dbo].[HitLog](
[ID] [int] IDENTITY(1,1) NOT NULL,
[url] [varchar](260) NOT NULL,
[user_name] [varchar](50) NOT NULL,
[hit_time] [datetime] NOT NULL,
[user_ip] [varchar](50) NULL,
CONSTRAINT [PK_HitLog] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE View [dbo].[Temp_Hits_per_hour] as
SELECT DATEADD(dd, 0, DATEDIFF(dd, 0, hit_time)) AS date_hit, DatePart(HH, hit_time) as [Hour], Count(*) as Total_Hits FROM HitLog
GROUP BY DatePart(HH, hit_time), DATEADD(dd, 0, DATEDIFF(dd, 0, hit_time))
CREATE VIEW [dbo].[View_HitByIP]
AS
SELECT COUNT(*) AS number_of_hits, DATEADD(dd, 0, DATEDIFF(dd, 0, hit_time)) AS date_hit, user_ip
FROM dbo.HitLog
GROUP BY user_ip, DATEADD(dd, 0, DATEDIFF(dd, 0, hit_time))
CREATE VIEW [dbo].[View_HitByURL]
AS
SELECT COUNT(*) AS number_of_hits, DATEADD(dd, 0, DATEDIFF(dd, 0, hit_time)) AS date_hit, url
FROM dbo.HitLog
GROUP BY url, DATEADD(dd, 0, DATEDIFF(dd, 0, hit_time))
Step2
Create a class library project to create a HTTPModule:
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Data;
using System.Data.SqlClient;
namespace HitModule
{
public class HitModule : IHttpModule
{
public void Init(HttpApplication app)
{
app.PreRequestHandlerExecute += new EventHandler(app_PreRequestHandlerExecute);
}
void app_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
string url = context.Request.Url.AbsoluteUri;
string userIP = context.Request.UserHostAddress;
string userName = context.User.Identity.Name;
string insertSql = "insert into HitLog(url, user_name, user_ip, hit_time) values('" + url + "','" + userName + "','" + userIP + "',getdate())";
SqlConnection newSqlConnection = new SqlConnection();
SqlCommand newSqlCommand = new SqlCommand(insertSql, newSqlConnection);
newSqlCommand.CommandType = System.Data.CommandType.Text;
newSqlConnection.ConnectionString = "user id=<userid>;password=<password>;Initial Catalog=<dbname>;Data Source=<sql server name>";
newSqlConnection.Open();
newSqlCommand.ExecuteNonQuery();
newSqlConnection.Close();
}
public void Dispose()
{
}
}
}
Step 3
Add the HTTPModule’s reference into the web.config file of your desired web application.
<add name="HitModule" type="HitModule. HitModule, HitModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9f4da00116c38ec5" />
Step 4
Create 3 parameterized reports using SQL Server Business Intelligence Development Studio. Here are the select commands for the datasets of those reports:
select top 20 URL, Sum(Number_of_hits) as total_number_of_hits,Convert(decimal(10,2), Sum(Number_of_hits))/DateDiff(dd, @StartDate, @EndDate) as Avg_Hits
from View_HitByURL
where date_hit between @StartDate and @EndDate
group by url
order by total_number_of_hits Desc
SELECT [Hour], SUM(Total_Hits) as Total_Hits,Convert(decimal(10,2), SUM(Total_Hits))/(DateDiff(dd, @StartDate ,@EndDate) + 1) as Avg_Hits FROM Temp_Hits_Per_Hour
WHERE
date_hit BETWEEN @StartDate AND @EndDate
GROUP BY [Hour]
ORDER BY 1, 2
select sum(number_of_hits) total_number_of_hits,Convert(decimal(10,2), Sum(number_of_hits))/DateDiff(dd, @StartDate, @EndDate) as Avg_Hits, user_ip
from View_HitByIP
where date_hit between @StartDate and @EndDate
group by user_ip
Step 5
Publish those reports in a dashboard page of your MOSS report center.
I used OpenXML to add the two metadata column values - document name as the header and modified date as footer while someone upload a document in document library. Please include a reference of WindowsBase and DocumentFormat.OpenXml in the project. WindowsBase is included in .Net framework 3.0/3.5 and you have to download DocumentFormat.OpenXml separately.
Here is the code:
using System;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;
using System.IO;
using System.IO.Packaging;
using DocumentFormat.OpenXml.Packaging;
using System.Xml;
using System.Collections.Generic;
namespace AddHeaderFooterReceiver
{
public class AddHeaderFooterEventReceiver : SPItemEventReceiver
{
public string GetFooter()
{
string footerVal = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><w:ftr xmlns:ve=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:m=\"http://schemas.openxmlformats.org/officeDocument/2006/math\" xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\" xmlns:w10=\"urn:schemas-microsoft-com:office:word\" xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" xmlns:wne=\"http://schemas.microsoft.com/office/word/2006/wordml\"><w:p w:rsidR=\"00C24C70\" w:rsidRDefault=\"00C24C70\"><w:pPr><w:pStyle w:val=\"Footer\" /></w:pPr><w:r><w:t>Hi</w:t></w:r></w:p><w:p w:rsidR=\"00C24C70\" w:rsidRDefault=\"00C24C70\"><w:pPr><w:pStyle w:val=\"Footer\" /></w:pPr></w:p></w:ftr>";
return footerVal;
}
public string GetHeader()
{
string headerVal = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><w:hdr xmlns:ve=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:m=\"http://schemas.openxmlformats.org/officeDocument/2006/math\" xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\" xmlns:w10=\"urn:schemas-microsoft-com:office:word\" xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" xmlns:wne=\"http://schemas.microsoft.com/office/word/2006/wordml\"><w:p w:rsidR=\"00C8737A\" w:rsidRDefault=\"00C8737A\"><w:pPr><w:pStyle w:val=\"Header\" /></w:pPr><w:r><w:t>hello</w:t></w:r></w:p><w:p w:rsidR=\"00C8737A\" w:rsidRDefault=\"00C8737A\"><w:pPr><w:pStyle w:val=\"Header\" /> </w:pPr></w:p></w:hdr>";
return headerVal;
}
public void WDAddHeader(Stream headerContent, Stream fileContent)
{
// Given a document name, and a stream containing valid header content,
// add the stream content as a header in the document.
const string documentRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
const string wordmlNamespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
const string headerContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml";
const string headerRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header";
const string relationshipNamespace = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
PackagePart documentPart = null;
using (Package wdPackage = Package.Open(fileContent, FileMode.Open, FileAccess.ReadWrite))
{
// Get the main document part (document.xml).
foreach (System.IO.Packaging.PackageRelationship relationship in wdPackage.GetRelationshipsByType(documentRelationshipType))
{
Uri documentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), relationship.TargetUri);
documentPart = wdPackage.GetPart(documentUri);
// There is only one officeDocument.
break;
}
Uri uriHeader = new Uri("/word/header1.xml", UriKind.Relative);
if (wdPackage.PartExists(uriHeader))
{
// Although you can delete the relationship
// to the existing node, the next time you save
// the document after making changes, Word
// will delete the relationship.
wdPackage.DeletePart(uriHeader);
}
// Create the header part.
PackagePart headerPart = wdPackage.CreatePart(uriHeader, headerContentType);
// Load the content from the input stream.
// This may seem redundant, but you must read it at some point.
// If you ever need to analyze the contents of the header,
// at least it is already in an XmlDocument.
// This code uses the XmlDocument object only as
// a "pass-through" -- giving it a place to hold as
// it moves from the input stream to the output stream.
// The code could read each byte from the input stream, and
// write each byte to the output stream, but this seems
// simpler...
XmlDocument headerDoc = new XmlDocument();
headerContent.Position = 0;
headerDoc.Load(headerContent);
// Write the header out to its part.
headerDoc.Save(headerPart.GetStream());
// Create the document's relationship to the new part.
PackageRelationship rel = documentPart.CreateRelationship(uriHeader, TargetMode.Internal, headerRelationshipType);
string relID = rel.Id;
// Manage namespaces to perform Xml XPath queries.
NameTable nt = new NameTable();
XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
nsManager.AddNamespace("w", wordmlNamespace);
// Get the document part from the package.
// Load the XML in the part into an XmlDocument instance.
XmlDocument xdoc = new XmlDocument(nt);
xdoc.Load(documentPart.GetStream());
// Find the node containing the document layout.
XmlNode targetNode = xdoc.SelectSingleNode("//w:sectPr", nsManager);
if (targetNode != null)
{
// Delete any existing references to headers.
XmlNodeList headerNodes = targetNode.SelectNodes("./w:headerReference", nsManager);
foreach (System.Xml.XmlNode headerNode in headerNodes)
{
targetNode.RemoveChild(headerNode);
}
// Create the new header reference node.
XmlElement node = xdoc.CreateElement("w:headerReference", wordmlNamespace);
XmlAttribute attr = node.Attributes.Append(xdoc.CreateAttribute("r:id", relationshipNamespace));
attr.Value = relID;
node.Attributes.Append(attr);
targetNode.InsertBefore(node, targetNode.FirstChild);
}
// Save the document XML back to its part.
xdoc.Save(documentPart.GetStream(FileMode.Create, FileAccess.Write));
}
}
public void WDAddFooter(Stream footerContent, Stream fileContent)
{
// Given a document name, and a stream containing valid footer content,
// add the stream content as a footer in the document.
const string documentRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
const string wordmlNamespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
const string footerContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml";
const string footerRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer";
const string relationshipNamespace = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
PackagePart documentPart = null;
using (Package wdPackage = Package.Open(fileContent, FileMode.Open, FileAccess.ReadWrite))
{
// Get the main document part (document.xml).
foreach (System.IO.Packaging.PackageRelationship relationship in wdPackage.GetRelationshipsByType(documentRelationshipType))
{
Uri documentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), relationship.TargetUri);
documentPart = wdPackage.GetPart(documentUri);
// There is only one officeDocument.
break;
}
Uri uriFooter = new Uri("/word/footer1.xml", UriKind.Relative);
if (wdPackage.PartExists(uriFooter))
{
// Although you can delete the relationship
// to the existing node, the next time you save
// the document after making changes, Word
// will delete the relationship.
wdPackage.DeletePart(uriFooter);
}
// Create the footer part.
PackagePart footerPart = wdPackage.CreatePart(uriFooter, footerContentType);
// Load the content from the input stream.
// This may seem redundant, but you must read it at some point.
// If you ever need to analyze the contents of the footer,
// at least it is already in an XmlDocument.
// This code uses the XmlDocument object only as
// a "pass-through" -- giving it a place to hold as
// it moves from the input stream to the output stream.
// The code could read each byte from the input stream, and
// write each byte to the output stream, but this seems
// simpler...
XmlDocument footerDoc = new XmlDocument();
footerContent.Position = 0;
footerDoc.Load(footerContent);
// Write the footer out to its part.
footerDoc.Save(footerPart.GetStream());
// Create the document's relationship to the new part.
PackageRelationship rel = documentPart.CreateRelationship(uriFooter, TargetMode.Internal, footerRelationshipType);
string relID = rel.Id;
// Manage namespaces to perform Xml XPath queries.
NameTable nt = new NameTable();
XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
nsManager.AddNamespace("w", wordmlNamespace);
// Get the document part from the package.
// Load the XML in the part into an XmlDocument instance.
XmlDocument xdoc = new XmlDocument(nt);
xdoc.Load(documentPart.GetStream());
// Find the node containing the document layout.
XmlNode targetNode = xdoc.SelectSingleNode("//w:sectPr", nsManager);
if (targetNode != null)
{
// Delete any existing references to footers.
XmlNodeList footerNodes = targetNode.SelectNodes("./w:footerReference", nsManager);
foreach (System.Xml.XmlNode footerNode in footerNodes)
{
targetNode.RemoveChild(footerNode);
}
// Create the new footer reference node.
XmlElement node = xdoc.CreateElement("w:footerReference", wordmlNamespace);
XmlAttribute attr = node.Attributes.Append(xdoc.CreateAttribute("r:id", relationshipNamespace));
attr.Value = relID;
node.Attributes.Append(attr);
targetNode.InsertBefore(node, targetNode.FirstChild);
}
// Save the document XML back to its part.
xdoc.Save(documentPart.GetStream(FileMode.Create, FileAccess.Write));
}
}
public override void ItemAdded(SPItemEventProperties properties)
{
string extension = properties.ListItem.Url.Substring(properties.ListItem.Url.LastIndexOf(".") + 1);
if (extension == "docx")
{
string headerContent = GetHeader().Replace("hello", properties.ListItem["Name"].ToString());
string footerContent = GetFooter().Replace("Hi", properties.ListItem["Modified"].ToString());
Stream headerStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(headerContent));
Stream footerStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(footerContent));
MemoryStream fileStream = new MemoryStream();
fileStream.Write(properties.ListItem.File.OpenBinary(), 0, (int)properties.ListItem.File.TotalLength);
WDAddHeader(headerStream, fileStream);
WDAddFooter(footerStream, fileStream);
properties.ListItem.File.SaveBinary(fileStream);
}
}
}
}
I saw a nice post in CodeProject (http://www.codeproject.com/KB/aspnet/CaptchaImage.aspx) regarding CAPTCHA image control. I used the same code to create a Custom Field that will work like a CAPTCHA field for SharePoint. I tested it with Custom List and Blog Post suceessfully. You need to enable the session in the Web.Config file using enableSessionState="true" as it depends heavily on session. Here is how it looks like:
Here is the screenshot of the Solution Explorer:
You can download the WSP file and the project in a zip file from here.
One of my friend and colleague asked me this question. I found it tricky and a good post for my blog. Here is what you have to do:
Go to your 12 hive\TEMPLATE\LAYOUTS\1033 folder. Open up the Core.js file. Find the function AddSendSubMenu. Go to the last 3 lines:
strAction = "STSNavigate('" + ctx.HttpRoot + "/_layouts/download.aspx?" + "SourceUrl=" + currentItemEscapedFileUrl + "&Source=" + GetSource() + "&FldUrl=" + escapeProperly(ctx.SendToLocationUrl) + "')"; ;
menuOption = CAMOpt(sm, L_DownloadACopy_Text, strAction, "");
menuOption.id = "ID_DownloadACopy";
Now if you want to remove "Download a Copy" option from all the document libraries then comment all three lines.
If you want to remove it for some custom List Definition that you have created you may do like:
if(ctx.listTemplate !=10001)
{
strAction = "STSNavigate('" + ctx.HttpRoot + "/_layouts/download.aspx?" + "SourceUrl=" + currentItemEscapedFileUrl + "&Source=" + GetSource() + "&FldUrl=" + escapeProperly(ctx.SendToLocationUrl) + "')"; ;
menuOption = CAMOpt(sm, L_DownloadACopy_Text, strAction, "");
menuOption.id = "ID_DownloadACopy";
}
Where 10001 was your custom list template id.
If you want to do it for a specific Document Library then you would do like this:
if(ctx.listName !="{28958B49-8C76-4987-815C-CF5A107FF522}")
{
strAction = "STSNavigate('" + ctx.HttpRoot + "/_layouts/download.aspx?" + "SourceUrl=" + currentItemEscapedFileUrl + "&Source=" + GetSource() + "&FldUrl=" + escapeProperly(ctx.SendToLocationUrl) + "')"; ;
menuOption = CAMOpt(sm, L_DownloadACopy_Text, strAction, "");
menuOption.id = "ID_DownloadACopy";
}
As you can see you have to pass the list GUID as listName.
Requirement: I have a custom desktop application and I have created an installer for it. During installation it needs to connect trough Internet to receive the key. Now I have stored these keys in a SharePoint List as given below:

The user will provide the Activation Key and Registration Number and will receive the Unlock Key to proceed with the software installation. We can use the OOB Lists.asmx web service to achieve this. The web method we are going to use is GetListItems.
I have created a Windows Application to test. Here I'll add Activation Key and Registration Number and will receive the Unlock Key.
In the application I add web reference of http://<site_url>/_vti_bin/lists.asmx with a name ListProxy.
Here is the code of my application:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
namespace TestKeyApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
ListProxy.Lists newListProxy = new ListProxy.Lists();
//change the username, password and domain with the value of a use who has permission to the list
newListProxy.Credentials = new System.Net.NetworkCredential("<username>", "<password>", "<domain>");
XmlDocument xmlDoc = new XmlDocument();
XmlNode ndQuery = xmlDoc.CreateNode(XmlNodeType.Element, "Query", "");
XmlNode ndViewFields = xmlDoc.CreateNode(XmlNodeType.Element, "ViewFields", "");
ndViewFields.InnerXml = "<FieldRef Name='Unlock_x0020_Key'/>";
ndQuery.InnerXml = "<Where><And><Eq><FieldRef Name='Activation_x0020_Key' /><Value Type='Text'>" + textBox1.Text + "</Value></Eq><Eq><FieldRef Name='Registration_x0020_Number' /><Value Type='Text'>" + textBox2.Text + "</Value></Eq></And></Where>";
try
{
XmlNode ndListItems = newListProxy.GetListItems("KeyList", null, ndQuery, ndViewFields, null, null, null);
//MessageBox.Show(ndListItems.OuterXml);
XmlNamespaceManager nsManager = new XmlNamespaceManager(ndListItems.OwnerDocument.NameTable);
nsManager.AddNamespace("z", "#RowsetSchema");
nsManager.AddNamespace("s", "uuid:BDC6E3F0-6DA3-11d1-A2A3-0AA00C14882");
nsManager.AddNamespace("dt", "uuid:C2F41010-65B3-11d1-A29F-00AA00C14882");
nsManager.AddNamespace("rs", "urn:schemas-microsoft-com:rowset");
XmlNodeList nodes = ndListItems.SelectNodes("rs:data/z:row", nsManager);
foreach (XmlNode node in nodes)
{
//label4 is where I am showing the unlock key
label4.Text += node.Attributes["ows_Unlock_x0020_Key"].Value;
}
}
catch (Exception ex)
{
MessageBox.Show("Please Enter correct activation key and registration number, message: " + ex.Message);
}
}
}
}
Office 2007 has an option to convert different types of Documents into PDF or XPS. You can download the add-in from this location:
http://www.microsoft.com/downloads/details.aspx?familyid=4D951911-3E7E-4AE6-B059-A2E79ED87041&displaylang=en
You can convert an existing word document or other office files to PDF or XPS by choosing Save As>PDF or XPS. We also can convert documents to PDF (or XPS) programmatically.
Here is a nice example given in MSDN to convert .docx to pdf:
http://msdn.microsoft.com/en-us/library/bb412305.aspx
I am adding the same example and additional codes to convert PowerPoint (.pptx) and Publisher (.pub) files to pdf. All these 3 examples are console applications.
For each of these go to Reference>Add Reference in Solution Explorer of VS 2008 and select COM tab. Add reference of Microsoft Word 12.0 Object Library in case of Word, Microsoft Publisher 12.0 Object Library in case of Publisher, and Microsoft PowerPoint 12.0 Object Library in case of PowerPoint.
Word
========
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Interop.Word;
namespace DocConverter
{
class Program
{
static void Main(string[] args)
{
ApplicationClass wordApplication = new ApplicationClass();
Document wordDocument = null;
object paramSourceDocPath = @"D:\Fun\Test.docx";
object paramMissing = Type.Missing;
string paramExportFilePath = @"D:\Fun\Test.pdf";
WdExportFormat paramExportFormat = WdExportFormat.wdExportFormatPDF;
bool paramOpenAfterExport = false;
WdExportOptimizeFor paramExportOptimizeFor =
WdExportOptimizeFor.wdExportOptimizeForPrint;
WdExportRange paramExportRange = WdExportRange.wdExportAllDocument;
int paramStartPage = 0;
int paramEndPage = 0;
WdExportItem paramExportItem = WdExportItem.wdExportDocumentContent;
bool paramIncludeDocProps = true;
bool paramKeepIRM = true;
WdExportCreateBookmarks paramCreateBookmarks =
WdExportCreateBookmarks.wdExportCreateWordBookmarks;
bool paramDocStructureTags = true;
bool paramBitmapMissingFonts = true;
bool paramUseISO19005_1 = false;
try
{
// Open the source document.
wordDocument = wordApplication.Documents.Open(
ref paramSourceDocPath, ref paramMissing, ref paramMissing,
ref paramMissing, ref paramMissing, ref paramMissing,
ref paramMissing, ref paramMissing, ref paramMissing,
ref paramMissing, ref paramMissing, ref paramMissing,
ref paramMissing, ref paramMissing, ref paramMissing,
ref paramMissing);
// Export it in the specified format.
if (wordDocument != null)
wordDocument.ExportAsFixedFormat(paramExportFilePath,
paramExportFormat, paramOpenAfterExport,
paramExportOptimizeFor, paramExportRange, paramStartPage,
paramEndPage, paramExportItem, paramIncludeDocProps,
paramKeepIRM, paramCreateBookmarks, paramDocStructureTags,
paramBitmapMissingFonts, paramUseISO19005_1,
ref paramMissing);
}
catch (Exception ex)
{
// Respond to the error
}
finally
{
// Close and release the Document object.
if (wordDocument != null)
{
wordDocument.Close(ref paramMissing, ref paramMissing,
ref paramMissing);
wordDocument = null;
}
// Quit Word and release the ApplicationClass object.
if (wordApplication != null)
{
wordApplication.Quit(ref paramMissing, ref paramMissing,
ref paramMissing);
wordApplication = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
}
PowerPoint
=============
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Interop.PowerPoint;
namespace PowerPointConverter
{
class Program
{
static void Main(string[] args)
{
Microsoft.Office.Interop.PowerPoint.Application ppApp = new Microsoft.Office.Interop.PowerPoint.Application();
Microsoft.Office.Interop.PowerPoint.Presentation presentation = ppApp.Presentations.Open(@"D:\Fun\Test.pptx", Microsoft.Office.Core.MsoTriState.msoTrue, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoFalse);
presentation.ExportAsFixedFormat(@"D:\Fun\Test.xps",
PpFixedFormatType.ppFixedFormatTypeXPS,
PpFixedFormatIntent.ppFixedFormatIntentPrint,
Microsoft.Office.Core.MsoTriState.msoFalse,
PpPrintHandoutOrder.ppPrintHandoutHorizontalFirst,
PpPrintOutputType.ppPrintOutputSlides,
Microsoft.Office.Core.MsoTriState.msoFalse,
null,
PpPrintRangeType.ppPrintAll,
"",
false,
false,
false,
true,
true,
System.Reflection.Missing.Value);
presentation.Close();
presentation = null;
ppApp = null;
GC.Collect();
}
}
}
Publisher
===============
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Interop.Publisher;
using System.Reflection;
namespace PublisherConverter
{
class Program
{
static void Main(string[] args)
{
Microsoft.Office.Interop.Publisher.Application pbApp = new Microsoft.Office.Interop.Publisher.Application();
Microsoft.Office.Interop.Publisher.Document pbDoc = pbApp.Open(@"D:\Fun\Test4.pub", true, true, PbSaveOptions.pbDoNotSaveChanges);
pbDoc.ExportAsFixedFormat(PbFixedFormatType.pbFixedFormatTypePDF, @"D:\Fun\Test.pdf", PbFixedFormatIntent.pbIntentPrinting, true, 300, 450, 1200, 1800, 1, pbDoc.Pages.Count, 1, false, PbPrintStyle.pbPrintStyleDefault, false, false, false, System.Reflection.Missing.Value);
pbDoc.Close();
pbDoc = null;
pbApp = null;
GC.Collect();
}
}
}
Requirement: This is an enhancement of the List Definition project we have created in previous post (SharePoint 2007 (MOSS/WSS) – How to add a new column in Discussion Board showing the Latest Thread for each Discussion – Step by Step). The new requirement is: I want to add new column that will fetch data from BDC data source. This can be done Out of the Box. But I have some additional requirements. I want this column to show CategoryName field from Categories table of Northwind database. And in Display view the column will have a hyperlink which will take you to http://www.contoso.com/?id=ID. Where ID is CategoryID from Categories table.
Step 1:
Start VS 2008 and open our previous project CustomDiscussionBoard and click OK. In the solution explorer right click on the project and choose Add>New Item>Field Control and change the name from FieldControl1 to DataSelector.
Step 2:
Select View from the Menu bar and select Other Windows>WSP View. Go back to Solution Explorer once again and open fldtypes_DataSelector.xml file. Between these 2 tags:
<Field Name="UserCreatable">TRUE</Field>
and
<Field Name="FieldTypeClass">3f2da0f5-4e0a-4d8d-a853-907816e06328</Field>
add following lines:
<Field Name="ShowOnListCreate">TRUE</Field>
<Field Name="ShowOnSurveyCreate">TRUE</Field>
<Field Name="ShowOnDocumentLibrary">FALSE</Field>
<Field Name="ShowOnColumnTemplateCreate">TRUE</Field>
<Field Name="Sortable">TRUE</Field>
<Field Name="Filterable">TRUE</Field>
Before </FieldType> tag, add the following lines:
<RenderPattern Name="DisplayPattern">
<HTML><![CDATA[<SCRIPT>var linkval="]]></HTML>
<Column/>
<HTML>
<![CDATA[";</SCRIPT>]]>
</HTML>
<HTML><![CDATA[<A HREF="]]></HTML>
<HTML><![CDATA[http://www.contoso.com?id=]]></HTML>
<Column/>
<HTML><![CDATA[">]]></HTML>
<HTML><![CDATA[<SCRIPT>document.write(linkval.substring(linkval.indexOf('$#')+2));</SCRIPT>]]></HTML>
<HTML><![CDATA[</A>]]></HTML>
</RenderPattern>
Step 3:
Right click on Templates folder in Solution Explorer and select Add>New Folder and rename it as controltemplates. Now right click on the controltemplates folder and select Add>New Item. In the Add New Item Window select Visual C# Project Items>text file. Change the name as DataSelector.ascx. Open the file and add these lines into the file:
<%@ Control Language="C#" %>
<%@ Assembly Name="CustomDiscussionBoard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9f4da00116c38ec5" %>
<%@ Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.WebControls" %>
<SharePoint:RenderingTemplate Id="DataSelectorTemplate" runat="server">
<Template>
<asp:Label runat=server ID="lblDataSelector" Text="Select Data:" />
<asp:DropDownList runat=server ID="lstDataSelector" />
</Template>
</SharePoint:RenderingTemplate>
Close the DataSelector.ascx file and open it once again.
Now build the solution and go to bin>debug folder at the solution’s physical file location. Copy the path from the address bar and open Visual Studio Command Prompt window. CD to the path copied above and issue this command sn –T CustomDiscussionBoard.dll. This will give you the public key token for the library. Copy it and replace the public key token given in this line at DataSelector.ascx file:
<%@ Assembly Name=" CustomDiscussionBoard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=
Step 4:
Write click on the project and select Add>New Item>Content Type. Change the name to CTDataSelector and click Add. Select the Base Content Type as Discussion and click Ok without selecting the checkbox.
Step 5:
In the solution explorer open the CTDataSelector.xml file under CTDataSelector folder. Add the following line within <FieldRefs> </FieldRefs> tags.
<FieldRef ID="{D23E7B2E-5895-45e4-AE40-8BDF4090DDD5}" Name="DataSelector" Required="TRUE" ShowInDisplayForm="TRUE" ShowInNewForm="TRUE" ShowInEditForm="TRUE" />
Before </Elements> Tag add following line:
<Field ID="{8DC58E9A-98D8-44e2-96E7-514CC9E2A32B}" Type="DataSelectorField" Name="DataSelector" DisplayName="DataSelector" Sealed="TRUE" StaticName="DataSelector" >
</Field>
Replace the GUIDs of FieldRef and Field tags using GUIDGen.
Also change the Description and Name tag to “Custom Discussion".
Step 6:
Copy the entire Field tag from CTDataSelector.xml and
Now open the scema.xml file under CustomDiscussionBoard folder and paste it within <Fields></Fields> tag after the first <Field></Field> tag.
Step 7:
Open CTDataSelector.xml once again and copy the <ContentType ID (copy the content within “”). Open Schema.xml file under CustomDiscussionBoard folder and in the first <ContentTypeRef ID Tag replace the content (within “”).
Step 8:
In the solution explorer add a new reference to microsoft.sharepoint.portal. You will find it under: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI\microsoft.sharepoint.portal.dll.
Now open DataSelector.FieldControl.cs file under DataSelector folder. Add these using statements:
using Microsoft.Office.Server.ApplicationRegistry.Runtime;
using Microsoft.Office.Server.ApplicationRegistry.Infrastructure;
using Microsoft.Office.Server.ApplicationRegistry.MetadataModel;
Change the class statement:
public class DataSelectorFieldControl : TextField
to
public class DataSelectorFieldControl : BaseFieldControl
Now add following lines within the opening and closing curly braces of class:
protected DropDownList lstDataSelector;
protected override string DefaultTemplateName
{
get { return "DataSelectorTemplate"; }
}
public override object Value
{
get
{
EnsureChildControls();
return lstDataSelector.SelectedValue.ToString() + "$#" + lstDataSelector.SelectedItem.Text;
}
set
{
try
{
EnsureChildControls();
string newValue = value.ToString();
lstDataSelector.Items.FindByValue(newValue.Substring(0, newValue.IndexOf("$#"))).Selected = true;
}
catch { }
}
}
protected override void CreateChildControls()
{
if (Field == null) return;
base.CreateChildControls();
//Don't render fields if we are just displaying the field
if (ControlMode == SPControlMode.Display)
return;
lstDataSelector = (DropDownList)TemplateContainer.FindControl("lstDataSelector");
if (lstDataSelector == null)
throw new ArgumentException("lstDataSelector is null. Corrupted DataSelector.ascx file.");
if (this.ControlMode == SPControlMode.Edit || this.ControlMode == SPControlMode.New)
{
// I have used Nothwind database use your own LOBSystem, LOBSystemInstances and Entities
LobSystem nwSystem = ApplicationRegistry.GetLobSystems()["NorthwindLOBSystem"];
LobSystemInstance nwSystemInstance = nwSystem.GetLobSystemInstances()["NorthwindInstance"];
Entity categoryEntity = nwSystem.GetEntities()["dbo.Categories"];
FilterCollection fc = categoryEntity.GetFinderFilters();
IEntityInstanceEnumerator nwInstanceEnumerator = categoryEntity.FindFiltered(fc, nwSystemInstance);
while (nwInstanceEnumerator.MoveNext())
{
IEntityInstance category = nwInstanceEnumerator.Current;
System.Web.UI.WebControls.ListItem newListItem = new System.Web.UI.WebControls.ListItem(category["CategoryName"].ToString(), category["CategoryID"].ToString());
lstDataSelector.Items.Add(newListItem);
}
}
}
Step 9:
Create a new BDC metadata file and upload it into your SSP. Here is the sample that I have used for the code. You can see I have used SQL user to connect to the database. You have to make suitable changes for your server, user, password etc.
Step 10:
Now Build the project and deploy it from the Build menu.
Open the destinantion site and go to Site Actions>Create. You will find CustomDiscussionBoard under Communications section. You can create a new discussion board or can use discussion board called testdisc that we have created previously. Once created and opened go to Settings and Discussion Board Settings. Under Views section select Flat. In the next window make sure Make this default view option is selected and select DataSelector column from the Columns section and click OK. Do the same with the Subject view too. Once done you can create new discussions and add replies to those discussions. You can see the DataSelector dropdown available as a new field. You can select a new value and in subject view you can see the CategoryName is shown with a link to http://www.contoso.com/?id=CategoryID$#CategoryName. So although it contains extra element ($#CategoryName) but we can still remove this part in the target page code and can get the desired result.
Step 1:
Start VS 2008 and select Visual C# > SharePoint. Select List Definition as the project template. Rename the project as CustomDiscussionBoard and click OK. In the List Definition Setting dialog box click OK.
Step 2:
In the solution explorer delete the ListDefinition1 folder and right click on the project and choose Add>New Item>List Definition and change the name from ListDefinition1 to CustomDiscussionBoard. In the List Definition Setting dialog box select Discussion Board and select Add with event receiver.
Step 3:
Under CustomDiscussionBoard folder click on ItemEventReceiver.cs file to open it. Just before the default constructor add this line:
string bodytext = "";
Step 4:
Uncomment the ItemAdded method and add the following lines there:
bodytext = properties.ListItem["Body"].ToString();
if (bodytext != "")
{
int startpos = bodytext.IndexOf("<div>") + 5;
int endpos = bodytext.IndexOf("</div>") - startpos;
string[] strArr = bodytext.Substring(startpos, endpos).Replace("<br>", "").Split(new Char[] { ' ' });
if (strArr.Length > 2)
{
properties.ListItem["LatestThread"] = strArr[0] + " " + strArr[1] + " " + strArr[2] + "....";
}
else if (strArr.Length == 2)
{
properties.ListItem["LatestThread"] = strArr[0] + " " + strArr[1];
}
else
{
properties.ListItem["LatestThread"] = strArr[0];
}
properties.ListItem.Update();
if (properties.ListItem.Folder != null)
{
string[] strArr2 = bodytext.Substring(startpos, endpos).Replace("<br>", "").Split(new Char[] { ' ' });
if (strArr2.Length > 2)
{
properties.ListItem.Folder.Properties["LatestThread"] = strArr2[0] + " " + strArr2[1] + " " + strArr2[2] + "....";
}
else if (strArr2.Length == 2)
{
properties.ListItem.Folder.Properties["LatestThread"] = strArr2[0] + " " + strArr2[1];
}
else
{
properties.ListItem.Folder.Properties["LatestThread"] = strArr2[0];
}
properties.ListItem.Folder.Update();
}
}
Step 5:
Uncomment the ItemUpdated method and add the following lines there:
SPList list = (SPList)properties.ListItem.ParentList;
SPQuery query = new SPQuery();
query.Folder = properties.ListItem.Folder;
SPListItemCollection listItemCol = list.GetItems(query);
foreach (SPListItem item in listItemCol)
{
bodytext += item["Correct Body To Show"].ToString();
}
if (bodytext != "")
{
int lastclasspos = bodytext.LastIndexOf("<div class") + 4;
int lastclosedivpos = bodytext.LastIndexOf("</div>");
string newbodytext = bodytext.Substring(lastclasspos, lastclosedivpos - lastclasspos);
int startpos = newbodytext.IndexOf("<div>") + 5;
int endpos = newbodytext.IndexOf("</div>") - startpos;
string[] strArr = newbodytext.Substring(startpos, endpos).Replace("<br>", "").Split(new Char[] { ' ' });
if (strArr.Length>2)
{
properties.ListItem["LatestThread"] = strArr[0] + " " + strArr[1] + " " + strArr[2] + "....";
}
else if (strArr.Length == 2)
{
properties.ListItem["LatestThread"] = strArr[0] + " " + strArr[1];
}
else
{
properties.ListItem["LatestThread"] = strArr[0];
}
properties.ListItem.Update();
}
Step 6:
Under CustomDiscussionBoard folder click on schema.xml file to open it. Find the <Fields> tag, under it after the first <Field></Field> tag, add the following tag:
<Field ID="{5915B79B-4218-4056-B8CA-E51BC8A5ED14}" Type="Text" Name="LatestThread" DisplayName="LatestThread" StaticName="LatestThread">
</Field>
Create a new GUID using GUIDGen and change the GUID in the ID attribute above.
Step 7:
Right click on the project in Solution Explorer and select properies. In the properties window go to Debug and in the Start Browser with URL option add the URL of your destination site. Now Build the site and deploy it from the Build menu.
Step 8:
Open the destinantion site and go to Site Actions>Create. You will find CustomDiscussionBoard under Communications section. Click on it and create a discussion board called testdisc. Once created and opened go to Settings and Discussion Board Settings. Under Views section select Flat. In the next window make sure Make this default view option is selected and select LatestThread column from the Columns section and click OK. Do the same with the Subject view too. Once done you can create new discussions and add replies to those discussions. You can see the LatestThread column will show the last thread value. If you have more than 3 words in the content it will show periods after 3 words when it will show the column.
Also see SharePoint 2007 (MOOS/WSS) – How to add a new column (Custom BDC Column) in Discussion Board (Part2) – Step by Step
Since my post “Adding user control to SharePoint 2007 (MOSS/WSS) Web Part and handling events in there” I got great response and several feedbacks. I am adding a new step by step process using VS 2008 (VSeWSS 1.2). I have also used properties for the Web Part which can be set from the Web Part task pane.
Step 1:
Start VS 2008 and select new Project and select Visual C#>SharePoint>Web Part. Change the name as ContosoUserControlWP. In the Solution explorer select the folder Webpart1 and delete it. Right click on the project and select Add>New Item. In the new item window select SharePoint and Web Part. Change the name of the Web Part as ContosoUserControlWP.
Step 2:
Right click on the project and select Add>New Folder. Name it Templates. Similarly create another folder under Templates and name it controltemplates. Right click on controltemplates and select Add>New Item. In the new item window select General and Text File. Change the name of the Text File as ContosoUserControl.ascx. Now from Toolbox add three TextBoxes and a button to the ContosoUserControl.ascx. You can also use table etc. for formatting. You may find that the design mode is not working for these User Controls. If required you can create a separate Visual Studio Web Application and can add web user control there and can add and see all these controls in design mode and can copy the HTML from there to our project. Otherwise you can drag the User Control from controltemplates folder to the root of Project (just outside Templates folder). Now you can see the design mode will work. Once your changes are done drag it back to the controltemplates folder.
Step 3:
After the opening curly braces of class ContosoUserControlWP add these lines:
UserControl userControl;
TextBox txtFirstName;
TextBox txtLastName;
TextBox txtFullName;
Button btnSubmit;
public enum displayFont
{
verdana = 0,
arial,
geogia
};
protected displayFont displayFontName;
[WebBrowsable(true), Personalizable(true), WebDisplayName("Disply Font"), WebDescription("Disply Font")]
public displayFont DisplayFontName
{
get{ return displayFontName; }
set{displayFontName = value;}
}
Remove all lines from CreateChildControls method and add following lines:
userControl = (UserControl)Page.LoadControl(@"/_controltemplates/ContosoUserControl.ascx");
txtFirstName = (TextBox)this.userControl.FindControl("TextBox1"); // IDs our textboxes in user control (TextBox1, TextBox2, TextBox3)
txtLastName = (TextBox)this.userControl.FindControl("TextBox2");
txtFullName = (TextBox)this.userControl.FindControl("TextBox3");
btnSubmit = (Button)this.userControl.FindControl("Button1");
btnSubmit.Click += new EventHandler(btnSubmit_Click);
this.Controls.Add(userControl);
Step 4:
After CreateChildControls method add these lines:
void btnSubmit_Click(object sender, EventArgs e)
{
txtFullName.Text = txtFirstName.Text + " " + txtLastName.Text;
txtFullName.Font.Name = displayFontName.ToString();
}
protected override void Render(HtmlTextWriter writer)
{
userControl.RenderControl(writer);
}
Step 5:
Right click on the project once again and go to properties and in the Debug tab and Start Browser with URL add the destination site’s URL. Now deploy the application from Build> Deploy menu and open the destination site’s URL .
Step 6:
In the site click on Site Actions>Edit Page and click on Add a Web Part in any Web Part Zone. This will open up the Web Part gallery and under Misceleneous section you will find ContosoUserControlWP Web Part. Select the Web Part and it will be added to the Web Part Zone. Now if you exit the edit mode you can see the Web Part with 3 textboxes and a button. Add your first and last name into the first and second textbox and hit the button. This will show up the full name in the 3rd textbox. Moreover if you click on Modify This Web Part in Edit Mode, it will show up the Web Part Properties Task Pane. In the Miscellaneous section you will find the font setting drop down and there you can change the font as per your choice and now if you hit the button in the Web Part once again you can see the full name in the specific font.
Project Requirement:
Contoso Inc. is a BPO organization working on Medical Transcription. Information workers listen to the voice recordings and create text files based on those recordings. 3 Text files (test.txt, test2.txt, test3.txt) are given as sample.
IT department of Contoso Inc. has implemented MOSS 2007 for their document management and back office solution. CTO wanted to store the above mentioned text files in special document libraries which will have following mandatory columns “Patient Name”, “Attending Doctor”, “Patient Number” and “Date of Service”.
Once an Information Worker will complete typing the text file, he/she will upload the text file to any of these document libraries and afore mentioned metadata columns will be populated automatically by selecting the necessary values from the text file. Associated values for these columns are stored at the beginning of the text file (see sample).
Solution:
We will create a custom content type including all these fields and will create a custom list definition which will use this custom content type and also have the necessary code to extract the metadata columns from the uploaded text file. Additionally we will add code to make sure that the users upload only text files.
Step1:
Start VS 2008 and select Visual C# > SharePoint. Select List Definition as the project template. Rename the project as ContosoTranscriptDocLib and click OK. In the List Definition Setting dialog box click OK.
Step2:
In the solution explorer delete the ListDefinition1 folder and right click on the project and choose Add>New Item>List Definition and change the name from ListDefinition1 to ContosoTranscriptDocLib. In the List Definition Setting dialog box select document library and select Add with event receiver.
Step3:
Again in the solution explorer right click on the project and choose Add>New Item>Content Type and change the name from ContentType1 to ContosoTranscriptDoc. In the Content Type Setting dialog box select document.
Step4:
In the solution explorer open the ContosoTranscriptDoc.xml file under ContosoTranscriptDoc folder. Add the following few lines within <FieldRefs> </FieldRefs> tags.
<FieldRef ID="{3154C55D-956E-4049-831F-39AFF3AB7787}" Name="Patient" Required="TRUE" ShowInDisplayForm="TRUE" ShowInNewForm="FALSE" ShowInEditForm="TRUE" />
<FieldRef ID="{4F294260-B36B-4fd0-A5A8-E2D777E76E73}" Name="Doctor" Required="TRUE" ShowInDisplayForm="TRUE" ShowInNewForm="FALSE" ShowInEditForm="TRUE" />
<FieldRef ID="{2EE0BFF8-FBEE-4f93-822B-608138CAB1FD}" Name="PNumber" Required="TRUE" ShowInDisplayForm="TRUE" ShowInNewForm="FALSE" ShowInEditForm="TRUE" />
<FieldRef ID="{5BF5C5BB-F020-453d-8194-B9553487051A}" Name="ServiceDate" Required="TRUE" ShowInDisplayForm="TRUE" ShowInNewForm="FALSE" ShowInEditForm="TRUE" />
Replace the GUIDs of each FieldRef using Tools>Create Guid menu.
Step5:
Before </Elements> Tag add following few lines:
<Field ID="{3154C55D-956E-4049-831F-39AFF3AB7787}" Type="Text" Name="Patient" DisplayName="Patient Name" Sealed="TRUE" StaticName="Patient">
</Field>
<Field ID="{4F294260-B36B-4fd0-A5A8-E2D777E76E73}" Type="Text" Name="Doctor" DisplayName="Attending Doctor" Sealed="TRUE" StaticName="Doctor">
</Field>
<Field ID="{2EE0BFF8-FBEE-4f93-822B-608138CAB1FD}" Type="Text" Name="PNumber" DisplayName="Patient Number" Sealed="TRUE" StaticName="PNumber">
</Field>
<Field ID="{5BF5C5BB-F020-453d-8194-B9553487051A}" Type="DateTime" Name="ServiceDate" DisplayName="Date of Service" Sealed="TRUE" StaticName="ServiceDate">
</Field>
Match the GUIDs of each Field with the corresponding FieldRef.
Step 6:
Change the Description attribute of ContentType Tag to "Contoso Transcript Content Type".
Step 7:
Open ListDefinition.xml file under ContosoTranscriptDocLib folder in the solution explorer and change the ListTemplate Tag as followed:
<ListTemplate Name="ContosoTranscriptDocLib"
DisplayName="Contoso Transcript Document Library"
Description="Document library to upload text file associated with Medical Transcription"
BaseType="1"
Type="101"
OnQuickLaunch="TRUE"
SecurityBits="11"
Sequence="110"
Image="/_layouts/images/itdl.gif"
DocumentTemplate="101" />
Step 8:
Open ContosoTranscriptDoc.xml once again and copy the <ContentType ID (copy the content within “”). Open Schema.xml file under ContosoTranscriptDocLib folder and in the <ContentTypeRef ID Tag replace the content (within “”). Copy the 4 field tags from ContosoTranscriptDoc.xml and paste it under <Fields> tag, before first <Field> tag.
Step 9:
Open ItemEventReceiver.cs file under ContosoTranscriptDocLib folder and add this using statement: using System.IO;. After the default constructor (ContosoTranscriptDocLibItemEventReceiver) add following few lines:
///////////////Custom Code/////////////////
/// <summary>
/// String between MR# and DATE
/// +4 is because "MR#:" itself has the length of 4 characters
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private string GetNo(string str)
{
int first = str.IndexOf("MR#:") + 4;
int last = str.LastIndexOf("DATE:");
string ouput = str.Substring(first, last - first);
return ouput.Trim();
}
/// <summary>
/// String between DOCTOR and SUBJECTIVE
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private string GetDoctor(string str)
{
int first = str.IndexOf("DOCTOR:") + 7;
int last = str.LastIndexOf("SUBJECTIVE:");
string ouput = str.Substring(first, last - first);
return ouput.Trim();
}
/// <summary>
/// String between DATE and DOCTOR
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private string GetDate(string str)
{
int first = str.IndexOf("DATE:") + 5;
int last = str.LastIndexOf("DOCTOR:");
string ouput = str.Substring(first, last - first);
return ouput.Trim();
}
/// <summary>
/// String between NAME and MR
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private string GetPatient(string str)
{
int first = str.IndexOf("NAME:") + 5;
int last = str.LastIndexOf("MR#:");
string ouput = str.Substring(first, last - first);
return ouput.Trim();
}
/// <summary>
/// Extract the content of the file in a string
/// </summary>
/// <param name="itemID"></param>
/// <param name="siteUrl"></param>
/// <param name="listTitle"></param>
/// <returns></returns>
private string GetContent(int itemID, string siteUrl, string listTitle)
{
SPWeb web = new SPSite(siteUrl).OpenWeb();
SPList list = web.Lists[listTitle];
SPListItem item = list.GetItemById(itemID);
TextReader tr = new StreamReader(item.File.OpenBinaryStream());
string content = tr.ReadToEnd();
tr.Close();
return content;
}
/////////////Custom Code//////////////////
Step 10:
Uncomment the ItemAdded method and add following lines there:
int itemID = properties.ListItem.ID;
string siteUrl = properties.WebUrl + "/";
string listTitle = properties.ListTitle;
string content = GetContent(itemID, siteUrl, listTitle);
string patient = GetPatient(content);
string doctor = GetDoctor(content);
string serviceDate = GetDate(content);
string patientNo = GetNo(content);
properties.ListItem["Patient Name"] = patient;
properties.ListItem["Attending Doctor"] = doctor;
properties.ListItem["Patient Number"] = patientNo;
properties.ListItem["Date of Service"] = DateTime.Parse(serviceDate);
properties.ListItem.Update();
Step 11:
Uncomment the ItemAdding method and add following lines there:
string extension = properties.AfterUrl.Substring(properties.AfterUrl.LastIndexOf(".") + 1);
if (extension != "txt")
{
properties.Cancel = true;
properties.ErrorMessage = "Please upload only text files";
}
Step 12:
Right click on the project in Solution Explorer and select properies. In the properties window go to Debug and in the Start Browser with URL option add the URL of your destination site. Now Build the site and deploy it from the Build menu.
Step 13:
Open the destinantion site and go to Site Actions>Site Settings>Site Content Type. You will find ContosoTranscriptDoc under Development section. Go to Site Actions>Create. You will find Contoso Transcript Document Library under Libraries section. Click on it and create a document libarary called testtranscript. Select None as the document template.
Step 14:
Once the document library is created, upload the first sample (test.txt). Once the file is uploaded, the Edit Item window will open. Fill up the Title field as “test” and click on Check In. Similalrly upload rest of the sample files and in each case you will see the fields “Patient Name”, “Attending Doctor”, “Patient Number” and “Date of Service” are auto populated. Now try to upload any file other than text files and it will show error saying "Please upload only text files".
This is the first non-SharePoint blog post I am writingJ. I am writing because this is interesting and it seems lots of people faced or facing similar issues. If you are storing images in Access DB table as OLE object, and you are adding images programmatically (for example uploading images from an aspx page), then you might not face this issue. This problem occurs when you are adding images directly into the Access database table after opening it through MS Access client. When you try to retrieve those images you will find those do not open in Web Pages or any other program. This is because Access adds OLE Header in front of those images.
Here is a function that will remove the OLE Header from different types of images. I found the original code with the example of BMP, JPEG images somewhere I forgot the source and it was also referred by Andy Spears (MS Partner). Rest is done by me.
private byte[] GetImageBytesFromOLEField(byte[] oleFieldBytes)
{
const string BITMAP_ID_BLOCK = "BM";
const string JPG_ID_BLOCK = "\u00FF\u00D8\u00FF";
const string PNG_ID_BLOCK = "\u0089PNG\r\n\u001a\n";
const string GIF_ID_BLOCK = "GIF8";
const string TIFF_ID_BLOCK = "II*\u0000";
byte[] imageBytes;
// Get a UTF7 Encoded string version
Encoding u8 = Encoding.UTF7;
string strTemp = u8.GetString(oleFieldBytes);
// Get the first 300 characters from the string
string strVTemp = strTemp.Substring(0, 300);
// Search for the block
int iPos = -1;
if (strVTemp.IndexOf(BITMAP_ID_BLOCK) != -1)
iPos = strVTemp.IndexOf(BITMAP_ID_BLOCK);
else if (strVTemp.IndexOf(JPG_ID_BLOCK) != -1)
iPos = strVTemp.IndexOf(JPG_ID_BLOCK);
else if (strVTemp.IndexOf(PNG_ID_BLOCK) != -1)
iPos = strVTemp.IndexOf(PNG_ID_BLOCK);
else if (strVTemp.IndexOf(GIF_ID_BLOCK) != -1)
iPos = strVTemp.IndexOf(GIF_ID_BLOCK);
else if (strVTemp.IndexOf(TIFF_ID_BLOCK) != -1)
iPos = strVTemp.IndexOf(TIFF_ID_BLOCK);
else
throw new Exception("Unable to determine header size for the OLE Object");
// From the position above get the new image
if (iPos == -1)
throw new Exception("Unable to determine header size for the OLE Object");
//Array.Copy(