您的位置:首页 > 其它

Custom Ribbon Action (DownLoad Multiple File) -------------SharePoint 2010

2011-02-24 10:00 579 查看
http://www.cnblogs.com/KingStar/archive/2010/10/13/1850034.html

Below is
a screenshot of my project




The first thing I
did was to create a new SharePoint 2010 Empty SharePoint Project called
DeviantPoint.DownloadZip. Then, I added a reference to
ICSharpCode.SharpZipLib.dll which is a part of SharpZipLib
(a .NET library that is well-used for working with
Zip files). I then created a helper class, ZipBuilder, used to actually create
the zip file. I created this helper class with the intent that I could reuse
this elsewhere, not just for this project.

Basically, when an
instance of this class is constructed, you need to pass in a stream that will be
used to write the contents of the zip file to. This could be any type stream
(FileStream, MemoryStream, etc). There are a couple of helper methods in this
class that allow you to add files and folders and method that “finalizes” the
zip file. This Finalize() method must always be called to ensure that the zip
file is written out correctly. This class also implements the IDisposable
pattern since it is handling streams.

This is the code
for the ZipBuilder class:





代码

using
System;

using
System.Collections.Generic;

using
System.Linq;

using
System.Text;

using
System.IO;

using
ICSharpCode.SharpZipLib.Zip;

using
ICSharpCode.SharpZipLib.Core;

namespace
SPMultipleFileDownLoad.Classes

{

public

class
ZipFileBuilder : IDisposable

{

private

bool
disposed
=

false
;

ZipOutputStream zipStream
=

null
;

protected
ZipOutputStream ZipStream

{

get
{
return
zipStream; }

}

ZipEntryFactory factory
=

null
;

private
ZipEntryFactory Factory

{

get
{
return
factory; }

}

public
ZipFileBuilder(Stream outStream)

{

zipStream
=

new
ZipOutputStream(outStream);

zipStream.SetLevel(
9
);

//
best compression

factory
=

new
ZipEntryFactory(DateTime.Now);

}

public

void
Add(
string
fileName, Stream fileStream)

{

//
create a new zip entry

ZipEntry entry
=
factory.MakeFileEntry(fileName);

entry.DateTime
=
DateTime.Now;

ZipStream.PutNextEntry(entry);

byte
[] buffer
=

new

byte
[
65536
];

int
sourceBytes;

do

{

sourceBytes
=
fileStream.Read(buffer,
0
, buffer.Length);

ZipStream.Write(buffer,
0
, sourceBytes);

}

while
(sourceBytes
>

0
);

}

public

void
AddDirectory(
string
directoryName)

{

ZipEntry entry
=
factory.MakeDirectoryEntry(directoryName);

ZipStream.PutNextEntry(entry);

}

public

void
Finish()

{

if
(
!
ZipStream.IsFinished)

{

ZipStream.Finish();

}

}

public

void
Close()

{

Dispose(
true
);

GC.SuppressFinalize(
this
);

}

public

void
Dispose()

{

this
.Close();

}

protected

virtual

void
Dispose(
bool
disposing)

{

if
(
!
disposed)

{

if
(disposing)

{

if
(ZipStream
!=

null
)

ZipStream.Dispose();

}

}

disposed
=

true
;

}

}

}

The next thing I wrote was SPExtensions.cs, a class for adding extension
methods to some of the Microsoft.SharePoint objects. This class basically just
adds a few simple helper methods to the SPListItem class and the SPList class.
For the SPListItem class, I just added a method to determine if the SPListItem
instance is actually a folder and for the SPList class, I added a method to
determine if the list is actually a document library.

The code for SPExtensions is below:





代码

using
System;

using
System.Collections.Generic;

using
System.Linq;

using
System.Text;

//
Add

using
System.Runtime.CompilerServices;

using
Microsoft.SharePoint;

namespace
SPMultipleFileDownLoad.Classes

{

public

static

class
SPExtensions

{

public

static

bool
IsFolder(
this
SPListItem item)

{

return
(item.Folder
!=

null
);

}

public

static

bool
IsDocumentLibrary(
this
SPList list)

{

return
(list.BaseType
==
SPBaseType.DocumentLibrary);

}

}

}

The next thing I did was to add a SharePoint Mapped Folder to my project
mapping to the Layouts directory located in the SharePoint root. When you add a
mapped folder, Visual Studio will automatically create a sub-folder in that
mapped folder with the same name as your project. This is a good thing as you
don’t want to be mixing up all your project files with all of the files that
come out of the box from SharePoint.

After I had my Layouts mapped folder and subfolder created, I added a
SharePoint 2010 Application Page item to the sub-folder called DownloadZip.aspx.
The purpose of this application page is to actually handle the request from the
client to build the zip file and send it back down to the client. Having an
application page handle this is the same technique that is used with the
‘Download a Copy’ action button you see in the SharePoint 2010 ribbon.
Basically, a POST request from a client is sent to my DownloadZip.aspx page and
this page takes care of packaging up a zip file and sending it down to the
client’s browser. This page expects two parameters:

sourceUrl –the full url of the document library (and folder, if inside of a
subfolder) where the request is being made from

itemIDs – a semi-colon delimited list of the SPListItem IDs that should be
included as part of the zip file. Note that folders also have ids so if a folder
is selected, that folder’s id would also be sent.

The code-behind for this application page basically takes the list of item
ids and for each item id, goes and grabs the corresponding file from the
document library in SharePoint and, using the ZipBuilder class, packages it up
as a zip file. If one of the items that was selected is actually a folder, it
will create that folder in the zip file as well and put all the items that are
in that SharePoint folder into the corresponding zip file folder. It will also
traverse through all the sub-folders in the hierarchy.

Below is the code-behind for the DownloadZip.aspx application page (there is
nothing I added to the Download.aspx file itself):

DownloadZip.aspx.cs





代码

using
System;

using
Microsoft.SharePoint;

using
Microsoft.SharePoint.WebControls;

using
System.IO;

using
System.Web;

using
SPMultipleFileDownLoad.Classes;

namespace
SPMultipleFileDownLoad.Layouts.SPMultipleFileDownLoad

{

public

partial

class
DownLoadZip : LayoutsPageBase

{

protected

void
Page_Load(
object
sender, EventArgs e)

{

string
fullDocLibSourceUrl
=
Request.Params[
"
sourceUrl
"
];

if
(
string
.IsNullOrEmpty(fullDocLibSourceUrl))
return
;

string
docLibUrl
=
fullDocLibSourceUrl.Replace(SPContext.Current.Site.Url,
""
);

SPList list
=
SPContext.Current.Web.GetList(docLibUrl);

if
(
!
list.IsDocumentLibrary())
return
;

string
pItemIds
=
Request.Params[
"
itemIDs
"
];

if
(
string
.IsNullOrEmpty(pItemIds))
return
;

SPDocumentLibrary library
=
(SPDocumentLibrary)list;

string
[] sItemIds
=
pItemIds.Split(
new

char
[] {
'
;
'
}, StringSplitOptions.RemoveEmptyEntries);

int
[] itemsIDs
=

new

int
[sItemIds.Length];

for
(
int
i
=

0
; i
<
sItemIds.Length; i
++
)

{

itemsIDs[i]
=
Convert.ToInt32(sItemIds[i]);

}

if
(itemsIDs.Length
>

0
)

{

using
(MemoryStream ms
=

new
MemoryStream())

{

using
(ZipFileBuilder builder
=

new
ZipFileBuilder(ms))

{

foreach
(
int
id
in
itemsIDs)

{

SPListItem item
=
library.GetItemById(id);

if
(item.IsFolder())

AddFolder(builder, item.Folder,
string
.Empty);

else

AddFile(builder, item.File,
string
.Empty);

}

builder.Finish();

WriteStreamToResponse(ms);

}

}

}

}

private

static

void
AddFile(ZipFileBuilder builder, SPFile file,
string
folder)

{

using
(Stream fileStream
=
file.OpenBinaryStream())

{

builder.Add(folder
+

"
//
"

+
file.Name, fileStream);

fileStream.Close();

}

}

private

void
AddFolder(ZipFileBuilder builder, SPFolder folder,
string
parentFolder)

{

string
folderPath
=
parentFolder
==

string
.Empty
?
folder.Name : parentFolder
+

"
//
"

+
folder.Name;

builder.AddDirectory(folderPath);

foreach
(SPFile file
in
folder.Files)

{

AddFile(builder, file, folderPath);

}

foreach
(SPFolder subFolder
in
folder.SubFolders)

{

AddFolder(builder, subFolder, folderPath);

}

}

private

void
WriteStreamToResponse(MemoryStream ms)

{

if
(ms.Length
>

0
)

{

string
filename
=
DateTime.Now.ToFileTime().ToString()
+

"
.zip
"
;

Response.Clear();

Response.ClearHeaders();

Response.ClearContent();

Response.AddHeader(
"
Content-Length
"
, ms.Length.ToString());

Response.AddHeader(
"
Content-Disposition
"
,
"
attachment; filename=
"

+
filename);

Response.ContentType
=

"
application/octet-stream
"
;

byte
[] buffer
=

new

byte
[
65536
];

ms.Position
=

0
;

int
num;

do

{

num
=
ms.Read(buffer,
0
, buffer.Length);

Response.OutputStream.Write(buffer,
0
, num);

}

while
(num
>

0
);

Response.Flush();

}

}

}

}

After creating the application page, I added a SharePoint 2010 Empty Element
item called DownloadZip to my project. This is nothing more than an Elements.xml
file that takes care of adding my custom action to ribbon
(CustomAction.Location=”CommandUI.Ribbon”). By default, for document libraries,
this is what the ribbon looks like:



I wanted to add my action inside of the area in the Documents tab, inside of
the Copies group so to do this, for the CommandUIDefinition, I set the Location
attribute to "Ribbon.Documents.Copies.Controls._children”. I also wanted it to
appear right after the Download a Copy action so for the Button element’s
Sequence attribute, I set the value to 15 (the Download a Copy button has a
sequence of 10 and the Send To button has a sequence of 20 so I needed to set
the sequence of my button to something in between). To understand where
everything is placed and what the sequences are by default, you need to look at
the file C:/Program Files/Common Files/Microsoft Shared/Web Server
Extensions/14/TEMPLATE/GLOBAL/XML/CMDUI.xml. I also specified the icons I wanted
to use for my action (these icons are also part of my project, located in a
sub-directory of the Images SharePoint mapped folder) and I also set the
TemplateAlias to “o1” so that my icon shows up large like Download a Copy does.
I also define the actual command handler in this Elements.xml file by adding a
CommandUIHandler element. The CommandAction attribute is used to specify what
exactly the button is supposed to do and the EnabledScript attribute is used to
determine whether or not the button/command is enabled. These two attributes’
values both point to javascript functions I define in a separate file (discussed
later). Because I’m using a separate javascript file, I also have to add another
CustomAction element in the Elements file that points to the location of my
javascript file. This is the result:

Enabled



Disabled



Below is the full Elements.xml file:

Elements.xml





代码

<?
xml version="1.0" encoding="utf-8"
?>

<
Elements
xmlns
="http://schemas.microsoft.com/sharepoint/"
>

<
CustomAction
Id
="DeviantPoint.DownloadZip"
Location
="CommandUI.Ribbon"
>

<
CommandUIExtension
>

<
CommandUIDefinitions
>

<
CommandUIDefinition
Location
="Ribbon.Documents.Copies.Controls._children"
>

<
Button
Id
="Ribbon.Documents.Copies.DownloadZip"
Command
="DownloadZip"
Sequence
="15"
Image16by16
="/_layouts/images/DeviantPoint.DownloadZip/zip_16x16.png"

Image32by32
="/_layouts/images/SPMultipleFileDownLoad/zip.png"

Description
="Download zip"
LabelText
="Download as Zip"

TemplateAlias
="o1"
/>

</
CommandUIDefinition
>

</
CommandUIDefinitions
>

<
CommandUIHandlers
>

<
CommandUIHandler
Command
="DownloadZip"
CommandAction
="javascript:downloadZip();"
EnabledScript
="javascript:enable();"
/>

</
CommandUIHandlers
>

</
CommandUIExtension
>

</
CustomAction
>

<
CustomAction
Id
="Ribbon.Library.Actions.Scripts"
Location
="ScriptLink"
ScriptSrc
="/_layouts/SPMultipleFileDownLoad/CustomActions.js"

/>

</
Elements
>

Finally, I created the CustomActions.js file. This file is used to define the
actions/behavior of my new ribbon button. The enable() function is used to
determine whether or not my button is enabled. If there is at least one item
selected, then my button is enabled. The downloadZip() function just starts off
the download process. Actually, I could have probably written the javascript so
I didn’t even need this function or calls to
SP.ClientContext.executeQueryAsync() but I was just trying to get something done
quickly and actually writing it this way gave me another place to show-off
another one of the UI features, the Status. If the call to
SP.ClientContext.executeQueryAsync() fails, then the onQueryFailed delegate is
executed. The onQueryFailed() function uses the SP.UI.Status to display the
error message, shown here:



The function onQuerySucceeded() is where the majority of the action happens.
I use the SP.ListOperation.Selection object to get a list of the selected items.
I then create a request to my DownloadZip.aspx application page and send that
page the list of selected item ids as well as the current url (the url of the
page the user is on). Like I said earlier, that application page takes care of
packaging everything up as a zip and streaming it down to the browser.

Below is the code for CustomActions.js:

CustomActions.js





代码

function
enable()

{

var
items
=
SP.ListOperation.Selection.getSelectedItems();

var
itemCount
=
CountDictionary(items);

return
(itemCount
>

0
);

}

function
downloadZip()

{

var
context
=
SP.ClientContext.get_current();

this
.site
=
context.get_site();

this
.web
=
context.get_web();

context.load(
this
.site);

context.load(
this
.web);

context.executeQueryAsync(

Function.createDelegate(
this
,
this
.onQuerySucceeded),

Function.createDelegate(
this
,
this
.onQueryFailed) ); }

function
onQuerySucceeded()

{

var
items
=
SP.ListOperation.Selection.getSelectedItems();

var
itemCount
=
CountDictionary(items);

if
(itemCount
==

0
)
return
;

var
ids
=

""
;

for
(
var
i
=

0
; i
<
itemCount; i
++
)

{

ids
+=
items[i].id
+

"
;
"
;

}

//
send a request to the zip aspx page.

var
form
=
document.createElement(
"
form
"
);

form.setAttribute(
"
method
"
,
"
post
"
);

form.setAttribute(
"
action
"
,
this
.site.get_url()
+

this
.web.get_serverRelativeUrl()
+

"
/_layouts/SPMultipleFileDownLoad/downloadzip.aspx
"
);

var
hfSourceUrl
=
document.createElement(
"
input
"
);

hfSourceUrl.setAttribute(
"
type
"
,
"
hidden
"
);

hfSourceUrl.setAttribute(
"
name
"
,
"
sourceUrl
"
);

hfSourceUrl.setAttribute(
"
value
"
, location.href);

form.appendChild(hfSourceUrl);

var
hfItemIds
=
document.createElement(
"
input
"
)

hfItemIds.setAttribute(
"
type
"
,
"
hidden
"
);

hfItemIds.setAttribute(
"
name
"
,
"
itemIDs
"
);

hfItemIds.setAttribute(
"
value
"
, ids);

form.appendChild(hfItemIds);

document.body.appendChild(form);

form.submit();

}

function
onQueryFailed(sender, args)

{

this
.statusID
=
SP.UI.Status.addStatus(
"
Download as Zip:
"
,
"
Downloading Failed:
"

+
args.get_message()
+

"
<a href='#' onclick='javascript:closeStatus();return false;'>Close</a>.
"
,
true
);

SP.UI.Status.setStatusPriColor(
this
.statusID,
"
red
"
);

}

So how does this actually all look when the user is using it? Below is the
hierarchy of an example document library I have:



Documents

Folder A (Folder)

Subfolder in Folder A (Folder)

Sub Sub Folder (Folder)

Versioning Flow (Visio diagram)

Business Brief_SoW (Word document)

SoW_Phase1 (Word document)

Request Email (Text file)

Users and Roles (Excel file)

Issues (Excel file)

Product_Planning (Excel file)

The user has selected some documents and a sub-folder so my custom ribbon
button is enabled:



The user clicks on this button and this feature executes and after it’s
complete the user is prompted with this (note, the filename is a timestamp):



Reference :http://www.deviantpoint.com/post/2010/05/08/SharePoint-2010-Download-as-Zip-File-Custom-Ribbon-Action.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: