Tuesday, September 7, 2010

SVN Trouble

(If all you want to do is solve the same problem, you only need to add a Timeout 1800 line to your c:\program files\visualsvn server\conf\httpd-custom.conf file and restart Visual SVN server - you can, of course, adjust the number of seconds as you like)

First off, if you are doing any kind of software development and you aren't using source control of some kind, you are missing out big time. I highly recommend source control. I use Visual SVN for the server and Smart SVN for the client, but there are lots of other great options out there. OK, on to the good stuff.

We have purchased licenses for RedGate's SQL Source Control, which allows you to use SQL Manager to check in SQL objects into a SVN repository. Very cool. I had been using this for a short while, and I soon saw this error when trying to scan for changes:

Could not read chunk Size: secure connection truncated

Toodledo

Having recently changed over to using a Droid Incredible, I'm having to rework some of my productivity tools to work with the Incredible. Part of that was finding a new GTD / Todo list app.

I had used OmniFocus on my Mac / iPod Touch, but ever since the iOS 4 upgrade, the only way to get it to sync is to wipe out the OmniFocus database on the iPod Touch and start over (My guess is that they updated their software to work with multitasking, and as a result it breaks with devices, like the iPod Touch 2G, that don't support multitasking on iOS4). Not a great solution. After waiting several months for a fix to this, I've given up and I looked around for something similar for my Droid. I strongly considered Remember The Milk, but the Mobile side of that app was rather weak.

I ended up settling on Toodledo. It needs some work on the ability to set custom filters, but overall it does the job I need it to do and it even has a widget for the Droid that shows my 'hotlist,', which is a huge help for me to quickly see the next 10 items on my list, color coded by priority.

Will I stick with it long term? I'm not sure. But so far I like it. I did spend a couple of hours transferring all of my todo items from OmniFocus to Toodledo the other night, so I'm committed to trying to make it work.

I use it to keep track of my grocery list, and I walk around with it at Kroger like a true geek, checking things off as I put them in my cart - my wife has had several good laughs at me as a result.

But I also use it to keep track of when to do maintenance on the cars and house, errands I need to run, etc. It's great to have a GTD tool that works on the web and syncs to my Droid.

Friday, July 2, 2010

Sharepoint 2007: 'Only their own' permission on Document Library - Update!

OK, after getting tired of manually setting this using Sharepoint Manager 2007, I decided to put together a solution. This solution provides a nice drop-down on the Document Library 'Security' menu like this:



And when you click on the 'Read/Write Security' link, you'll see this:



It looks and acts just like the page for standard lists - but this one also works for document libraries.

Download the solution from codeplex at http://moresharepoint.codeplex.com

Wednesday, June 30, 2010

Sharepoint 2007: Picture Library limitations

In working with the Picture Library in Sharepoint 2007, I've run across 2 limitations:

1. No web part connections. For some reason Microsoft decided that Picture Libraries would never need web part connections. It's greyed out, as seen here:



2. Picture Libraries do not have the option to give users permissions to only view the pictures they created (and not view pictures that other users created). You can do this on non-document, non-picture library lists. Here is what it looks like on the 'Advanced Settings' page for a list:


But you don't get that on a picture library.

Thursday, June 17, 2010

Sharepoint 2007: 'Only their own' permission on Document Library

In Sharepoint 2007 you have a great permission you can set: 'Only their own'. This allows someone to view and edit only the items that they have created, and not other items that other people have created. Administrators of the site can see everything, of course.

This is easy to set on a List under the list settings. However, if you are using a Document Library, the setting is nowhere to be found. It is there, but it's hidden.

To change this setting, you can either write some code, as described here, or you can do the equivalent with Sharepoint Manager (make sure you get the 2007 version). Open Sharepoint Manager (while logged into your Sharepoint Server), drill down to the Document Library you are interested in, and you should see the 'WriteSecurity' setting. Change it to 2 to effectively set it to 'only their own.'

However, keep in mind that this only changes what the user can do via the UI. If they get to the list data via web services or some other method, this won't prevent them from getting at other people's data.

Friday, May 28, 2010

Passing a table using Nav Automation

I just had someone comment on my previous blog post asking how to pass a table using Nav Automation. Great question.

You can pass complex objects using Nav Automation, but only ones that you define (or that have been explicitly written to work with COM). This means that you can't pass a DataTable object, but you can create your own implementation of a DataTable object and pass it.

In your complex object, you have to explicitly define every property and method that you need to call on that complex object and surface those properties and methods through COM.

Tuesday, March 30, 2010

x does not contain a definition for y

I just ran across this problem... again... and because I didn't note the solution last time I had to figure it out again. It's so simple - but so hard to remember. Let's hope that next time I hit this I'll at least remember that I blogged about it. Here we go.

I'm building a web site, and it makes use of some DLLs that come from some C# libraries I've built. Over the course of time, I've added some functions to the libraries that I use in my website. And the website picks up the new functions and everything is great.

Then one day I come along and try to build the website in Visual Studio, but it gives me an error because it says that one of the newer functions in my library is missing. Here's the error:
className does not contain a definition for functionName
Hmm. I check the other project (which lives in the same solution), and the function is there. So I go to my web control, which is generating the error, I right click on the function name, and click 'Go to Definition', and sure enough, it jumps right to the function.

After a couple of hours of removing pieces and parts from different projects and trying to narrow things down, all without any avail, a thought finally hits my brain. The GAC!

Sure enough, an older version of my library somehow ended up in the GAC. I removed it from the GAC, then everything built just fine.

FYI, to remove a library from the GAC, browse in windows explorer to c:\windows\assembly, right click on the dll you want to remove, and click 'uninstall.'

Tuesday, October 20, 2009

Linq causes the Setup project to fail when building

All of a sudden, my setup project stopped building. It was very strange - when I tried to build it, it would say that the build failed, but it gave no errors. Nothing anywhere.

Thankfully, I'm not the first person to have run across this. It turns out that adding Linq to one of the projects in my solution caused this failure - it appears to be a bug in Visual Studio.

The solution appears at Microsoft's Connect site (be sure to look in the 'Workarounds' tab) - https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=317870

Here is what Daren wrote:

1. Close VS 2008.
2. Open the project file containing the LINQ To SQL item in Notepad.
3. Remove the following lines:
<ItemGroup>
<Service Include="{3259AA49-8AA1-44D3-9025-A0B520596A8C}" />
</ItemGroup>

The Setup Project will now build successfully. However, if you double-click the DBML file to open the designer in VS 2008 the Setup Project will stop building again. The above lines do *not* get re-added to the project file but the Setup Project will stop building anyway. Just restart VS 2008 and it will work again -- until you open the DBML designer again. Once the Setup Project fails due to this problem it will never build successfully until after you restart VS 2008.

Friday, October 9, 2009

OOO, C#, and C/AL

I've been working with building some web services built on the Microsoft Dynamics Nav 2009 web services platform. Microsoft is doing something fairly slick here - you write code in the C/AL language, save it as a Nav Object, expose it as a web service, and the web services layer automatically translates the C/AL to C# and runs it in .Net. This allows you to debug the code using visual studio, among other things.

But it's not perfect. Sometimes it makes mistakes.

I had this line of code in C/AL
IF AVSetup."Sub Award Dimension No." - 1 IN [1..8] THEN BEGIN
Which auto-translated to this in C#:
if(aVSetup.Target.
GetFieldValueSafe(90, NavType.Integer).ToInt32()-(1 >= 1 && 1 <= 8))
Oops. The order of operations got mixed up. The C/AL code subtracts one, then sees if the value is between 1 and 8. The C# sees if 1 is between 1 and 8 (getting a boolean value) and then tries to subtract the boolean value from the number. C# then gives this error:
Operator '-' cannot be applied to operands of type 'int' and 'bool'
Thankfully, the solution for this is simple. All you have to do is use extra parentheses to explicitly set the Order of Operations.
IF ((AVSetup."Sub Award Dimension No." - 1) IN [1..8]) THEN BEGIN

Monday, October 5, 2009

SQL Reporting Services, Sharepoint, and Firefox

I ran into a problem when I tried to use Firefox to view a SQL report on Sharepoint. In IE it works fine, but when it hits firefox, a 'display: block-inline' CSS style set on a table buried way down in the code caused firefox to do this:


The report is squeezed into a little IFrame, and you don't even get a vertical scrollbar. Sure, you can right-click on it and get your browser to show that IFrame as the main page, but that's not a good solution for end users. So I set about trying to figure out how to take care of this.

I found all kinds of helpful information about the problem. Lots of people had solutions, but none of them worked for me. Here is some of what I found:

Part of the problem is that I'm using SQL Reports with Sharepoint in Sharepoint Integration mode, whereas they seem to be using standable SQL Reports.

So I started digging. I used Firebug, a great tool by the way, to dig around the DOM to find out what was causing the problem. After drilling down through countless nested tables, I discovered that there was one control in particular that was causing the trouble:
<table id="m_sqlRsWebPart_ctl06_ctl13" cellspacing="0" cellpadding="0" style="overflow: auto; display: inline-block; width: 100%; height: 100%;">
In particular, the 'display:inline-block' didn't get along so well with firefox. When I removed that with Firebug, the report showed up just great. But the problem is that this HTML code is actually assigned by Microsoft.ReportingServices.Sharepoint.UI.WebParts.DLL (or by something that it calls), and modifying the DLLs isn't really an option.

After searching around, I found out that you can have a CSS declaration in a stylesheet override an element CSS declaration. These guys clued me in to this gem:
So I went to the reports server and went into the 12 hive at 12\TEMPLATE\LAYOUTS\ReportServer\RSViewerPage.aspx, and inside the HEAD element I added this:
<STYLE type="text/css">
table#m_sqlRsWebPart_ctl06_ctl13[style] {
display: table !important;
}
</STYLE>

Et Voila, it works! This chunk of CSS overrides what the DLL puts on the table element and changes the display property from inline-block to table. That then causes Firefox to behave properly and show the report.

Well, mostly. The other thing you need to do is open your report designer, open the report, and create a blank textbox that spans the width of the report. That will make sure that nothing on the right side of the report gets cut off.

Tuesday, March 10, 2009

Sharepoint Thumbnails and AssetUploader.aspx

We recently had a consultant build a web part for us that displayed thumbnails from images in a document library. In digging around in the code they gave us I discovered that they used a page built into Sharepoint called AssetUploader.aspx (in 12\TEMPLATE\LAYOUTS\AssetUploader.aspx, available off any web at _layouts\AssetUploader.aspx). This page is a rather handy. It's specifically designed to take a URL of an available image, resize it, and spit out a thumbnail. You can use it in any page or web part in Sharepoint by creating an IMG tag like this:
<img src="http://www.blogger.com/_layouts/AssetUploader.aspx?Size=Medium&ImageURL=/mysitecollection/mysite/MyImagesDocumentLibrary/IMG1234.jpg" alt="My Image" />
If you use a size of 'Small', it will make the largest dimension of the image 70 pixels, and if you use anything other than small (Medium, Large, etc.), it will make the largest dimension of the image 140 pixels.

It worked wonderfully in our test environment, but once we brought it into production, it broke. All we saw were little generic white document icons - no thumbnails. Yikes!

I turned, of course, to google, and I found a very helpful post from Bob Moore at PointBridge, where he explained the problem and provided some code as a solution. His code was tremendously helpful, but didn't get me all the way there, so I went to reflector myself to try and disassemble and fix it on my own.

However, interestingly, reflector couldn't disassemble all of it. The main class I was interested in was Microsoft.SharePoint.Publishing.Internal.CodeBehind.AssetThumbnailerPage. So I opened it in reflector to find this message when I tried to disassemble the OnLoad event:
// This item is obfuscated and cannot be translated.
I soon found that while Reflector couldn't show me the C# code for that function, I could look at the IL code for it (to do this, click on the language drop down in the toolbar and change it from C# or Visual Basic to IL). I'd never seen this before, but once I found Microsoft's IL reference, I was able to decipher and disassemble it by hand back into C#.

Here is the solution I ended up with that works well with AAM. I created a simple ASPX page containing only this:
<%@ Assembly Name="MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0123456789abcdef"%>
<%@ Page Language="C#" Inherits="MyNamespace.MyAssetThumbnailerPage" %>

And here is the code for the MyAssetThumbnailerPage class, which I included in a DLL that gets deployed via a feature to the GAC:
using System;
using System.Drawing;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Security.Permissions;
using System.IO;
using System.Globalization;
using System.Drawing.Imaging;
using System.Web;
using System.Collections;

namespace MyNamespace
{
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public partial class MyAssetThumbnailerPage : UnsecuredLayoutsPageBase
{

protected override void OnLoad(EventArgs e)
{
int Size;
SPWeb web = null;
SPSite site = null;
try
{
string ImageURL = Request.QueryString["ImageURL"].ToString();
if (Request.QueryString["Size"].ToString() == "Small")
{
Size = 70;
}
else
{
Size = 140;
}

Uri uri = new Uri(SPContext.Current.Web.Url);
string url = uri.Scheme + "://" + uri.Host + ImageURL;
Uri uri2 = new Uri(HttpContext.Current.Request.Url, url);

site = SPContext.Current.Site;
site = new SPSite(SPContext.Current.Web.Url);
SPSecurity.CatchAccessDeniedException = false;
web = site.OpenWeb();
SPFile file;
file = (SPFile)web.GetFileOrFolderObject(uri2.AbsoluteUri);

SPSecurity.CatchAccessDeniedException = true;

if (file == null)
{
Server.Transfer("/_layouts/IMAGES/ICGEN.GIF");
}

if (!IsImageUrl(file.Url))
{
Server.Transfer("/_layouts/IMAGES/" + file.IconUrl);
}

byte[] fileBytes = MyAssetThumbnailer.CreateThumbnail
(file.OpenBinary(),Size);


Response.OutputStream.Write(fileBytes, 0, fileBytes.Length);

base.OnLoad(e);
}
catch (Exception ex)
{
base.OnLoad(e);
}
finally
{
web.Close();
web.Dispose();
site.Close();
site.Dispose();
}
}

private static bool IsImageUrl(string fullUrlToTest)
{
if (!string.IsNullOrEmpty(fullUrlToTest))
{
string str = fullUrlToTest;
int index = fullUrlToTest.IndexOf
("?", StringComparison.Ordinal);

if (-1 == index)
{
index = fullUrlToTest.IndexOf
("#", StringComparison.Ordinal);

}
if (-1 != index)
{
str = fullUrlToTest.Substring(0, index);
}
string str2 = string.Empty;
if (!string.IsNullOrEmpty(str))
{
index = str.LastIndexOf(".", StringComparison.Ordinal);
if (-1 != index)
{
str2 = str.Substring(index);
}
}
//ULS.SendTraceTag(ULSTagID.tag_6osa,
//ULSCat.msoulscat_CMS_Publishing, ULSTraceLevel.Medium,
//"Verifying that the extension [%s] of URL ['%s' - full: " +
//"'%s'] is a recognized image type.",
//new object[] { str2, str, fullUrlToTest });

if (string.IsNullOrEmpty(str2))
{
return false;
}
ArrayList list = new ArrayList(new string[] { ".bmp", ".gif",
".jpeg", ".jpg", ".jpe", ".png" });

return list.Contains(str2.ToLowerInvariant());
}
return false;
}



}

internal class MyAssetThumbnailer
{
private MyAssetThumbnailer()
{

}

public static byte[] CreateThumbnail(byte[] inputBuffer,
int thumbMaxDimension)

{
MemoryStream stream = new MemoryStream(inputBuffer);
Bitmap bitmap = new Bitmap(stream);
Size imageSize = bitmap.Size;
Size size2 =getThumbnailDimensions(imageSize, thumbMaxDimension);
if (size2 == imageSize)
{
object[] objArray = new object[] {
thumbMaxDimension.ToString(CultureInfo.InvariantCulture),
imageSize.Width.ToString(CultureInfo.InvariantCulture),
imageSize.Height.ToString(CultureInfo.InvariantCulture),
inputBuffer.Length.ToString(CultureInfo.InvariantCulture)
};

//ULS.SendTraceTag(ULSTagID.tag_6osl,
//ULSCat.msoulscat_CMS_Publishing, ULSTraceLevel.Medium,
//"No thumbnail made - thumbnail size [%s] is greater " +
//"than image dimensions[%sx%s] bytes: %s", objArray);

return inputBuffer;
}
object[] data = new object[] {
size2.Width.ToString(CultureInfo.InvariantCulture),
size2.Height.ToString(CultureInfo.InvariantCulture),
imageSize.Width.ToString(CultureInfo.InvariantCulture),
imageSize.Height.ToString(CultureInfo.InvariantCulture),
inputBuffer.Length.ToString(CultureInfo.InvariantCulture)
};

//ULS.SendTraceTag(ULSTagID.tag_6osm,
//ULSCat.msoulscat_CMS_Publishing, ULSTraceLevel.Medium,
//"Generating thumbnail with dimensions[%sx%s] for image with " +
//"dimensions[%sx%s] bytes: %s", data);

return imageObjectToBytes(
bitmap.GetThumbnailImage(
size2.Width,
size2.Height,
new System.Drawing.Image.GetThumbnailImageAbort
(MyAssetThumbnailer.thumbnailCallback),
IntPtr.Zero
)
);

}

private static Size getThumbnailDimensions
(Size imageSize, int thumbMaxDimension)

{

if ((imageSize.Height > thumbMaxDimension) ||
(imageSize.Width > thumbMaxDimension))

{
SizeF ef = new SizeF((float)thumbMaxDimension,
(float)thumbMaxDimension);

if (imageSize.Height <= imageSize.Width)
{
ef.Height = (imageSize.Height * thumbMaxDimension) /
imageSize.Width;

}
else
{
ef.Width = (imageSize.Width * thumbMaxDimension) /
imageSize.Height;

}
if (ef.Width <>
{
ef.Width = 1f;
}
if (ef.Height <>
{
ef.Height = 1f;
}
if (ef.Width > thumbMaxDimension)
{
ef.Width = thumbMaxDimension;
}
if (ef.Height > thumbMaxDimension)
{
ef.Height = thumbMaxDimension;
}
return Size.Ceiling(ef);
}
return imageSize;
}

private static byte[] imageObjectToBytes
(System.Drawing.Image imageObject)

{
MemoryStream stream = new MemoryStream();
imageObject.Save(stream, ImageFormat.Jpeg);
return stream.ToArray();
}

private static bool thumbnailCallback()
{
return false;
}
}
}

Wednesday, December 3, 2008

NAV Automation Object

It took me a good while, but I finally managed to get my first automation object created, compiled, and made visible in NAV. Stefano Demiliani provided most of the guidance that I needed on this, as well as a few postings on mibuso, but there were a few other tricks I picked up on in the process that I figured I'd share here. Also, since I couldn't find one anywhere else, I figured I'd post a complete Visual Studio sample project.

You can download the sample project at http://bitsofinsanity.blob.core.windows.net/public/NavAutomation.zip

Please note that I am far from an expert on COM objects - I figured out just enough to get my code to compile and work, but it's highly likely that my work is suboptimal, so if anyone has any ideas for improvement, please share them.

I suppose I should explain what I'm talking about. Microsoft Dynamics NAV is an ERP system that contains and manages our Finances, Accounting, HR, Events, and more. It has tables of data (backed by SQL Server), forms to display the data, reports, and more. Each of these object types can have code behind it controlling what it does. However, the extensibility is limited to using COM objects, which are called 'Automation Objects' in NAV. But so long as you can create a COM object properly, you can insert it into a table or form and, through that COM object, you can do whatever the .Net framework allows you to do.

In working to create a COM object, I already had a class and and assembly that performed the functionality I wanted. I wanted to wrap it with COM so that I could see my class and its methods and functions in Nav. I first tried by adding the COM tags to my existing class, and I could kindof-sortof get it to work, but it never really worked completely. So I ended up creating a separate assembly and a separate class that contained the COM wrapper. All it does is call the real class, which does the hard work (the sample code doesn't call any other classes, but you could easily change it to do so).


Notes on creating an Automation Object

Your assembly will need to be strongly named. To do this:
  1. Right-click on your project name, click 'Properties', click the 'Signing' tab on the left
  2. Check the 'Sign the assembly' checkbox'
  3. In the 'Choose a strong name key file' drop down, choose 'New'. Give it a file name (anything you want), and uncheck the 'Protect my key file with a password' checkbox. Click 'OK'.

Please note the Post-build events in the sample code (under the project Properties). Make sure the paths to gacutil and regasm are correct. I don't know why, but for some reason I found I had to run the regasm and gacutil commands twice to make the COM object visible in Nav, hence the duplication of the lines below. These should register it on your development box so that you can see it in Nav. If you want to use the automation object elsewhere, you'll have to register the assembly in a similar manner on the other client machines. The post build events are:
SET GACUTIL="C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\gacutil.exe"
SET REGASM="C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm.exe"


%REGASM% $(TargetFileName) /tlb:NavAutomation.tlb

%GACUTIL% /i $(TargetFileName)


%REGASM% $(TargetFileName) /tlb:NavAutomation.tlb

%GACUTIL% /i $(TargetFileName)

After registering the assembly and type library, open Nav, open an object, and create a variable of type 'Automation.' For the subtype, click on the '...' then click on the drill-up arrow by 'Automation Server.' That will list all the COM objects loaded in your system, and you should be able to find your custom Automation object by the Assembly name (for the sample code, it should be called NavAutomation).


Be sure to create your own GUIDs for your project. The GUIDs you need to change are the ones on the interface and class definition in the cs file.

The sample project is built in Visual Studio 2008 with C# on .Net 3.5, and I tested using it on Nav 5.0 SP1, all running on Windows Server 2003 with SP2. It should work with other versions of Visual Studio, .Net, Nav, and Windows, but I haven't tested those out.

If you have an overloaded function in C#, the COM object representation in Nav will change it around a little. For example, if you have overloaded a function called 'test' with 3 different definitions, it will show up as 'test', 'test_2', and 'test_3.' This is done automatically. See the Nav symbol menu to find out what the names actually come up as.

There is no automatic type conversion between Decimal values in .Net and Decimal values in Nav, so you'll have to do a work around if you need to use decimal for a parameter or return value. I used a string object, casting it appropriately on both sides.

The Nav side can't handle constructors that require parameters. To get around this, you can create a constructor with no parameters and a function called 'init' that handles the actual construction logic.