.NET, SharePoint, Technical

Web.Config FeatureReceiver Update

After my previous post outlining my custom Feature Receiver used to update any section of a web.config file, I had some follow up email conversations with Mike about his post and us possibly collaborating on a combined CodePlex solution.  Well, that hasn’t happened yet.  However, during that discussion, we talked a bit about security.  Mike referred me to a post by a colleague of his inspired by this very topic.  The gist of the discussion and post is that modifying the web.config with a Feature Receiver really only makes sense, from a security perspective, when the Feature is scoped at the web level.  I agree, so I needed to make a small change to my custom FeatureReceiver.

The change I needed to make was simply to make sure that it did not update the web.config if the Feature was not deployed at the web scope.  This was a pretty simple change, but I took the opportunity to do a bit more research on the topic to see what others had done.  The best nugget I pulled out of this research time was a change in the way I was applying the updates.  Turns out there is an issue applying an update using

SPFarm.Local.Services.GetValue< SPWebService>().ApplyWebConfigModifications();

Using that method, the changes don’t get applied to the entire farm.  Since we’re in a farm environment, I would’ve run into that at some point.  The solution is to use this instead:

webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();

Continue to the end of this post for some more reading on the topic.  For now, here is the code update I made:

   1: private void UpdateWebConfig(string XPathLocation, string ElementName,  
   2:     Dictionary<string, string> Attributes, bool removeModification)
   3: {
   4:     try
   5:     {
   6:         SPWebApplication webApp = null;
   7:  
   8:         //Get the web app
   9:         webApp = _properties.Feature.Parent as SPWebApplication;
  10:  
  11:         if (webApp == null)
  12:             throw new SPException("Error obtaining reference to Web application. A feature must " +
  13:                 "be deployed at the WebApplication level to use the WebConfigChanges feature.");
  14:  
  15:         SPWebConfigModification modification =
  16:             new SPWebConfigModification(ElementName + CreateAttributeString(Attributes), XPathLocation);
  17:             //new SPWebConfigModification("authorizedType[@Assembly="Company.Moss.Activities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9eed2245513232a4"][@Namespace="Company.Moss.Activities"][@TypeName="*"][@Authorized="True"]", "configuration/System.Workflow.ComponentModel.WorkflowCompiler/authorizedTypes");
  18:  
  19:         modification.Owner = "Company.Moss.FeatureReceiver.FDFeatureReceiver";
  20:         modification.Sequence = 0;
  21:         modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
  22:         modification.Value =
  23:              string.Format(CultureInfo.InvariantCulture,
  24:              CreateModificationValueString(ElementName, Attributes),
  25:              CreateModificationValueArgs(Attributes));
  26:  
  27:         if (removeModification)
  28:             webApp.WebConfigModifications.Remove(modification);
  29:         else
  30:             webApp.WebConfigModifications.Add(modification);
  31:  
  32:         //Changing from SPFarm to webApp as advised by http://www.crsw.com/mark/Lists/Posts/Post.aspx?ID=32
  33:         //SPFarm.Local.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
  34:         webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
  35:         webApp.Update();
  36:  
  37:     }
  38:     catch (Exception ex)
  39:     {
  40:         System.Diagnostics.EventLog el = new System.Diagnostics.EventLog();
  41:         el.Source = "WebConfigFeature";
  42:         el.WriteEntry(ex.Message, System.Diagnostics.EventLogEntryType.Error);
  43:     }
  44: }

Like I said, this is going to look a lot like the code in my previous post.  The first change is in the first few lines of the try block.  I assume the parent of the Feature is a Web App.  If it gets a reference, great.  If it doesn’t, it logs an error and doesn’t do anything else.  It would be nice if there were a way to actually stop the Feature from being deployed, but that can’t be done in a Feature Receiver (it happens after the Feature is activated.)

The second change is at the end of that code snippet, and is to account for the Farm update issue I mentioned earlier.

That’s it.  Like I said, not much has changed, but I wanted to put out an update in case others are using this approach.

For further reading, here are some references:

Reza Alirezaei – SPWebModification’s Top 6 Issues

Mark Wagner – How To Modify the web.config File in SharePoint Using SPWebConfigModification

Serge van den Oever – SharePoint Features – elements, scope and other info

(That last one is useful because he lists the types of features that are allowed at each scope.  Can save you some time so you don’t implement this method to deploy something that can’t be deployed at the WebApplication level.)

.NET, InfoPath, SharePoint, Technical

InfoPath Master/Detail with SharePoint List Sources

I picked up some InfoPath requirements after one of our consultants had to leave so this one had to be done after dusting off the InfoPath brain cells.  I’m still not convinced this is the best way to get this done so please leave a comment if there’s a better way.

The InfoPath form needs to connect to two different SharePoint lists.  The first list is a list of Orders and the second is a list of Versions that apply to an Order.  There can be multiple Versions for each Order.  The goal was to create a master/detail type action where a user selects an order from a drop down, which then reloads the form with all of the version(s) and version details for that order displayed in a repeating table.  Doesn’t sound too hard, but I ran into some road blocks and had to resort to code.

Both roadblocks had to do with SharePoint lists as a source.  I don’t know if this is specific to SharePoint sources or just external sources in general, but you can’t use a SharePoint source in a master/detail relationship.  After dragging it onto the form, right click on the repeating table and view the properties.  The master/detail section is grayed out.  The second thing I ran into was trying to filter the SharePoint connection.  No luck there either, it’s not available.

After some searching, I found a post on implementing master/detail for a web based form which detailed some steps to pull in SharePoint data using a different method.  Instead of using the SharePoint List option when creating the connection, you use the XML option and point it to a URL which outputs the list data in XML format.  I won’t cover the details of that here as he covers it his post very well, but it’s using owssvr.dll.  That post solved my issues completely, but it was using code for InfoPath 2007.  I needed to convert that to be 2003 compatible. 

Since the 2003 compatible code is probably the only useful part of this post, here it is:

   1: public void OrderName_OnAfterChange(DataDOMEvent e)
   2: {
   3:     //Capture the new value for Order so we can filter the data just for that order
   4:     string newOrder = e.NewValue.ToString();
   5:  
   6:     //Get a reference to the Card Version Adapter
   7:     XMLFileAdapterObject cardVersion = thisXDocument.DataAdapters["Card Version owssvr"] as XMLFileAdapterObject;
   8:  
   9:     //Modify the Card Version adapter URL to append the filter
  10:     cardVersion.FileURL = cardVersion.FileURL + "&FilterField1=Order_x0020_Name&FilterValue1=" + newOrder;
  11:     
  12:     //Re-query
  13:     cardVersion.Query();
  14:  
  15:     if (e.IsUndoRedo)
  16:     {
  17:         // An undo or redo operation has occurred and the DOM is read-only.
  18:         return;
  19:     }
  20:  
  21:     // A field change has occurred and the DOM is writable.
  22:     // Write your code here.
  23: }

Pretty simple, but it took some SDK searching to find the right classes.  I’m using the XMLFileAdapterObject to grab the “Card Version owssvr” data connection, then I change the URL using FileURL similar to how Ishai modified the FileLocation property of the FileQueryConnection object.  Like I said, nothing fancy, but I thought it may come in handy for folks looking for a solution similar to Ishai’s, but who can’t use InfoPath 2007.