Awai, delivery guy

1 hour for cheese, 1 hour for supreme. What the heck?

  Home  |   Contact  |   Syndication    |   Login
  5 Posts | 0 Stories | 10 Comments | 0 Trackbacks

News

Archives

Post Categories

Biztalk Weblogs

Monday, July 09, 2007 #

Uploading a file to a Windows SharePoint Services 3.0 document library from external applications can be done in 2 steps using a combination of HTTP PUT and the Lists Web service.

Firstly, for the HTTP PUT side of things, check out: http://blogs.msdn.com/rohitpuri/archive/2007/04/10/upload-download-file-to-from-wss-document-library-using-dav.aspx .  In the code to follow, I use a variation of Rohit's helper class that takes a byte[].

Secondly, use the Lists Web service UpdateListItems() method to do the update.  But how do we get the ID from the HTTP PUT?  Turns out you don't need it.  Providing the FileRef field is enough.  For example, if you uploaded the file to http://mycompany/hr/Documents/bonus.xls, that's exactly what you'd put in FileRef, as shown below.

        public static string UploadFile(string url, string listName, byte[] document, 
                               List<KeyValuePair<string, object>> fileProperties)
        {
            string s = DAVHelper.UploadFile(url, document);
            if(s != "success")
                throw new Exception("File upload to: " + url + " failed.");

            // Update the attributes
            string updateRes = UpdateFileAttributes(url, listName, fileProperties);
            return updateRes;
	}

        public static string UpdateFileAttributes(string fileUrl, string listName, 
                                List<KeyValuePair<string, object>> fileProperties)
        {
            WssLists.ListsSoapClient listService 
                = new WssLists.ListsSoapClient("ListsSoap");
            listService.ClientCredentials.Windows.AllowedImpersonationLevel 
                = System.Security.Principal.TokenImpersonationLevel.Delegation;

            // Note that when building the Method fields, the field named ID
            // is required, although in this case we can and will leave it empty.
	    // (Concatenated empty string is just for visuals ;)
            StringBuilder sb = new StringBuilder();
            sb.Append("<Method ID='1' Cmd='Update'>");
            sb.Append("  <Field Name='ID'/>" + "" + "</Field>");
            sb.Append("  <Field Name='FileRef'>" + fileUrl + "</Field>");

	    // This is what the loop is basically doing:
            // sb.Append("  <Field Name='CustomColumn1'>" + "Some value" + "</Field>");
            for (int i = 0; i < fileProperties.Count; i++)
            {
                sb.Append("  <Field Name='" + fileProperties[i].Key + "'>" + 
                           Convert.ToString(fileProperties[i].Value) + "</Field>");
            }

            sb.Append("</Method>");

            XmlDocument xmlDoc = new System.Xml.XmlDocument();
            System.Xml.XmlElement elBatch = xmlDoc.CreateElement("Batch");

            elBatch.SetAttribute("OnError", "Continue");
            elBatch.SetAttribute("PreCalc", "TRUE"); 
            elBatch.SetAttribute("ListVersion", "0"); 

            elBatch.InnerXml = sb.ToString();

	    // Do the update
            XmlNode ndReturn = listService.UpdateListItems(listName, elBatch);

            return ndReturn.OuterXml;
        }

Friday, July 06, 2007 #

Having just gone through this, I thought I'd share the basic procedure when calling WSS 3.0 Web services such as lists.asmx and views.asmx from a WCF client.  As you may know, the exceptions returned by the WSS Web services are terse.  Hopefully, this post will help someone trying to avoid those in their own project.

Step 1 - Add a Service Reference to the WCF service you want to call (Duh)

Note that I included the "?wsdl".  If you don't VS will be redirected to /_vti_bin/Lists.asmx.  That doesn't matter from a service client code generation standpoint, just that the WCF endpoint addresses in App.config will need to be changed as they'll be pointing to the wrong URL, and none of the lists and other objects in your site will be found by the Web services.   

Step 2 - Modify the App.Config file to change the security binding configuration used by the client

In the basicHttpBinding section, replace:

<security mode="None">
    <transport clientCredentialType="None"
               proxyCredentialType="None" realm="" />
    <message clientCredentialType="UserName" algorithmSuite="Default" />
</security>

With:

<security mode="TransportCredentialOnly">
    <transport clientCredentialType="Ntlm" />
</security>

Step 3 - Allow the Web service to impersonate the user

WssList.ListsSoapClient listService = new WssList.ListsSoapClient("ListsSoap");
listService.ClientCredentials.Windows.AllowedImpersonationLevel
    = System.Security.Principal.TokenImpersonationLevel.Delegation;

XmlElement elem = listService.GetListCollection();

 

End transmission...


Monday, November 07, 2005 #

The following code is for quick and dirty command line program that generates an XML instance from an XML-serializable .NET class.  It uses BizTalk 2004's instance generation capabilities, so if you happen to find that useful, this code allows you to use it with .NET classes.  For example, let's say you have a complex configuration class ServiceConfiguration in assembly c:\whatever\TheSystem.Configuration.dll, and you compile the code below to btsi.exe, the following command will generate a sample instance:

btsi.exe “c:\whatever\TheSystem.Configuration.dll” ServiceConfiguration “c:\whatever\ServiceConfigurationSample.xml”

Here's the code.  Just paste it into a console app's default class, add a reference to:
{BizTalkInstallPath}\Developer Tools\Microsoft.BizTalk.TOM.dll
and build.

It's attrociously commented, but then again, painlessly simple:

using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml.Serialization;

using Microsoft.BizTalk.TOM;

namespace btsi
{
 ///


 /// Console application that generates a sample XML instance for a user
 /// supplied .NET type.  See usage for details.
 ///

 class Program
 {
  ///
  /// The main entry point for the application.
  ///

  [STAThread]
  static void Main(string[] args)
  {
   // Check args
   if(args.Length < 3)
   {
    PrintUsage();
    System.Environment.Exit(1);
   }

   // Gather args
   string sourceAssembly = args[0];
   string typeName = args[1];
   string outFile = args[2];

   // Get schema
   string schema = GetSchemaFromType(sourceAssembly, typeName);
   Console.WriteLine(schema);

   // Write instance
   CreateXmlInstance(schema, outFile);

  }

  private static void PrintUsage()
  {
   System.Console.WriteLine("Usage: {0} sourceAssembly typeName instanceFilename", Path.GetFileName(System.Reflection.Assembly.GetExecutingAssembly().Location));
  }

  private static string GetSchemaFromType(string file, string typeName)
  {
   XmlReflectionImporter importer = new XmlReflectionImporter();
   XmlSchemas schemas = new XmlSchemas();
   XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);

   // Enumerate the types, looking for the requested one
   Assembly assem = Assembly.LoadFrom(file);
   Type[] allTypes = assem.GetTypes();

   for(int i = 0; i < allTypes.Length; i++)
   {
    Type t = allTypes[i];
    if(t.IsPublic && !t.IsInterface)
    {
     if (t.FullName == typeName || t.Name == typeName)
     {
      XmlTypeMapping xtm = importer.ImportTypeMapping(t);
      exporter.ExportTypeMapping(xtm);
      break;
     }
    }
   }

   StringBuilder sb = new StringBuilder();
   if(schemas.Count > 0)
   {
    StringWriter sw = new StringWriter(sb);
    schemas[0].Write(sw);
    sw.Close();
   }

   return sb.ToString();

  }

  private static void CreateXmlInstance(string schema, string outFile)
  {
   string errorString = "";

   // Load the schema
   Microsoft.BizTalk.TOM.CEditorSchemaTree edTree = new CEditorSchemaTree();
   edTree.LoadFromString(schema, "", out errorString);
   edTree.DetermineDisplayRootReferenceOfSchema();

   InstanceGenerationOptions igo = new InstanceGenerationOptions();
   igo.CycleExpansionDepth = 0;
   igo.MaxNodeCount = 0;
   igo.UseNativeExtension = false;
   
   ITOMErrorInfo[] errors = null;

   // Write the instance
   edTree.CreateXMLInstance(outFile, igo, out errors);
  }
 
 }
}


Friday, February 17, 2006 #

In CSF 2.5 the Session.config setting ParticipantConfiguration/ProxyCacheDirectoryPath is used to specify the directory for the WSDL cache used by CSF when Sessions are created.  If you're a developer intent on implementing a particular service contract, forgetting about this cache might cost you a couple of hours of frustration. 

 

Let's say I build a service at http://MyMachine/MyVdir/sl.ashx implementing SOAP action MyAction. I then try to create a Session in which the service is a participant, but unfortunately, the Session expects my service to have SOAP action http://MyCompany/MyAction.  CSF will create a cache file consisting of the URI as the top line, and the first draft WSDL as the remainder of the file.  Until the cache expires (and the default is 24 hours), nothing done to fix the action will work; not building, not bouncing IIS, etc. Until you delete the cached WSDL or it expires, CSF will find the file based on the URI, and use the WSDL from it.

 

Relevant Session.config settings:

ParticipantConfiguration/ProxyCacheDirectoryPath

ParticipantConfiguration/ProxyCacheFileExpirationTime

Default cache directories in CSF 2.5: 

Developer Edition: C:\Program Files\Microsoft CSF\CSFDeveloper\Temp\

Standard edition: C:\csf\session\participant\cache\*.*


Wednesday, April 05, 2006 #

If you get “Unable to Attach to Process” inside Visual Studio 2003 when trying to attach to a BTSNTSvc.exe process for CLR type debugging, if .NET Framework 2.0 is installed on the machine, add the following to BTSNTSvc.exe.config:

<startup>
<requiredRuntime version="v1.1.4322" />
</startup>

I know I've seen this posted somewhere before, but had a hard time tracking it down again.  ...means one more posting is needed IMO.

Later!