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;
}
}
}