Before anything, I have to say that I do not advocate uncritical use of this solution. A good system should validate all input and keep its entities valid at all times. If you end up needing this one, you shold be needing it because you're creating a quick fix in a legacy system, or you're working with something that _should_ have errors. :)

My particular need just now is that I'm introducing a dropdown for a freetext field in a system I inherited. Its using LINQ to SQL datasources and no business layer what-so-ever, and I'm not about to make on on my 4 hour budget.

Anyway, according to this feedback post, I'm guessing MS isn't gonna bake this into the .net framework anytime soon.

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=260603

So here's an inherited DropDownList that does exactly that. :)
(Pretty quick and easy, so I guess that's why we don't get one in the box)

Note: It iterates over the items in the list one extra time, so it does degrade performance!

public class ForgivingDropDownList : DropDownList
{
	[Category("Behavior"), DefaultValue(true)]
	public bool AllowInvalidSelectedValue
	{
		get { return ViewState["allowInvalid"] != null ? (bool)ViewState["allowInvalid"] : true; }
		set { ViewState["allowInvalid"] = value; }
	}

	public override string SelectedValue
	{
		get
		{
			return base.SelectedValue;
		}
		set
		{
			if (!AllowInvalidSelectedValue)
			{
				base.SelectedValue = value;
				return;
			}

			if (this.Items.Count != 0)
			{
				if ((value == null) || (base.DesignMode && (value.Length == 0)))
				{
					this.ClearSelection();
					return;
				}
				ListItem item = this.Items.FindByValue(value);
				if (item == null)
				{
					base.SelectedValue = null;
					return;
				}

				base.SelectedValue = value;
			}
		}
	}
}

And here's some simple sample usage:

<asp:FormView ID="FormView1" runat="server">
	<EditItemTemplate>
		<cc1:ForgivingDropDownList ID="ForgivingDropDownList1" runat="server" SelectedValue='<%# Bind("Text") %>'>
			<asp:ListItem>a</asp:ListItem>
			<asp:ListItem>b</asp:ListItem>
		</cc1:ForgivingDropDownList>
	</EditItemTemplate>
</asp:FormView>


public class Thing
{
	public string Text { get; set; }
}

public partial class ForgivingDropDownPage : System.Web.UI.Page
{
	protected void Page_Load( object sender, EventArgs e )
	{
		if (!IsPostBack)
		{
			FormView1.DefaultMode = FormViewMode.Edit;
			FormView1.DataSource = new Thing[] { new Thing { Text = "c" } };
			FormView1.DataBind();
		}
	}
}

And that's it. :) Enjoy!

 

Posted 6. juli 2009 20:18 by lea | with no comments
Filed under: ,

I've been writing one too many protected void OnThisEvent() and OnThatEvent() handlers lately, and what's bugging me the most is having to if (Event != null) every time.

So I wrote a wee little helper giving me just a wee bit less code to write when I want to fire an(y) event.

Trick is it has to be used from the exact class (even if the event is in the base class) the event is declared, so you still need protected void OnThisEvent() for base classes to fire it, but it's still a bit neater I think. It kind of says "I'm telling my observers this happened" instead of "I might be doing something now". :)

(Of course encapsulating the event firing in a common method as OnEvent() is also necessary when anything else than firing the event should be done)

Here's the class:

public static class Fire
{
	public static object Event(Delegate theDelegate, params object[] args)
	{
		if (theDelegate != null)
			return theDelegate.DynamicInvoke(args);
		return null;
	}
}

And here's some code showing it off..

// Forwarding "menu" click to presenter
protected void repeater_ItemCommand(object source, RepeaterCommandEventArgs e)
{
	Fire.Event(NavigatingTo, this, new NavigatingEventArgs(e.Item.ItemIndex));
}

// Exposing event to derived classes
protected void OnViewInitialized()
{
	Fire.Event(ViewInitialized, this, EventArgs.Empty);
}

// Which is just a wee bit cooler than...
protected void OnViewInitialized()
{
	if (ViewInitialized != null)
		ViewInitialized(this, EventArgs.Empty);
}


 

Posted 4. juni 2009 05:43 by lea | with no comments
Filed under: ,

I had a small challenge today (yay!) when trying to localize some strings based on a boolean in the domain.

Boolean's ToString() methods only returns the static strings "True" and "False", hence String.Format(MyLocalizedString, true) cannot return "ja", "sant", or any other norwegian representations of the bool true.

Enter the FormattableBool which interprets a conditional operator in the format string. :)

	public struct FormattableBool : IFormattable
	{
		static FormattableBool fbTrue = new FormattableBool(true);
		static FormattableBool fbFalse = new FormattableBool(false);

		bool value;

		public FormattableBool(bool value)
		{
			this.value = value;
		}

		#region IFormattable Members

		public string ToString(string format, IFormatProvider formatProvider)
		{
			if (format.StartsWith("LC"))
			{
				if (!(format.Contains("?") && format.Contains(":")))
					throw (new InvalidOperationException("Format does not contain ? and :"));

				string[] formatParts = format.Split("?:".ToCharArray());
				if (formatParts.Length != 3)
					throw (new InvalidOperationException("Format is invalid"));

				return value ? formatParts[1] : formatParts[2];
			}
			else
				return ToString();
		}

		#endregion

		public static implicit operator FormattableBool(bool x)
		{
			return x ? fbTrue : fbFalse;
		}

		public static explicit operator bool(FormattableBool x)
		{
			return x.value;
		}

		public override bool Equals(object obj)
		{
			if (obj is bool)
				return value.Equals(obj);
			if (obj is FormattableBool)
				return value.Equals(((FormattableBool)obj).value);
			return false;
		}

		public override int GetHashCode()
		{
			return value.GetHashCode();
		}
	}

Turns out it can be used in several forms, where my favorite must be (FormattableBool)true.

	[TestMethod]
	public void TestFormattableBoolFormatting()
	{
		bool yep = true;
		bool nope = false;

		FormattableBool fyep = true;
		FormattableBool fnope = false;

		Assert.AreEqual(yep, fyep);
		Assert.AreEqual(nope, fnope);

		Assert.AreEqual("ja", String.Format("{0:LC?ja:nei}", fyep));
		Assert.AreEqual("nei", String.Format("{0:LC?ja:nei}", fnope));

		Assert.AreEqual("yep", String.Format("{0:LC?yep:nope}", (FormattableBool)true));
		Assert.AreEqual("nope", String.Format("{0:LC?yep:nope}", (FormattableBool)false));

	}

 

Posted 28. mai 2009 22:38 by lea | with no comments
Filed under: ,

I just read an article about norwegian organizations who suggest a bunch of road requirements today. It struck me that they fit well with programming practices:

«The Peoples Road Requirements»

  • 1. Norway should have a good and uniform roadnet securing access for all.
  • 2. The roads must be modelled so that human errors don't get fatal consequences.
  • 3. The roads shall be maintained continously and repaired when not accoring to fixed standard.
  • 4. The roads must be developed so the negative impact from traffic on the enviornment is as small as possible.
  • 5. The roads shall have a standard fit for todays needs so it is possible to plan and carry out the journey as expected.
  • 6. The road users are entitled to good and correct information about road- and snow conditions.

Source: http://www.aftenposten.no/nyheter/iriks/article2954264.ece

Posted 2. mars 2009 17:49 by lea | with no comments
Filed under:

I've been sure the ASP.NET theme framework fixed the url's of pictures with equal paths under the application as the themes.
Looks like it doesn't. The only way to theme images is to have a skin file in each theme.

Why would I want to duplicate SkinID="thisImage" ImageUrl="Images/ThisImage.gif" for all my themes if the images are named the same?

The solution is batch files.

Having the following bat file in my solution dir, I can spread a common skin file containing image urls to all my themes:

for /f "delims=" %%i in ('dir "%~1App_Themes" /ad/b') do copy "%~1App_Themes\Images.skin" "%~1\App_Themes\%%i\Images.skin"

now I just run "$(SolutionDir)\SpreadSkin.bat" "$(ProjectDir)" in my pre build even and I've got the skin file in all theme folders.

I got the brilliant idea of displaying multiple queues today, and boldly stated it could be done easily with SharePoint and URL addressable views.

Well, I was wrong, although the solution is quite simple.

CRM Queues does not support URL addressing as is, so I had to create something.

After poking around Microsoft.Crm.Web.dll a bit using Reflector, I found that the queue id of your personal queue is set during OnPreRender.
So I made a copy of the /workplaces/home_workplace.aspx file calling it home_workplace_parameter.aspx, and inserted the following after the last <%@ Import %> statement:

<script runat="server" language="C#">

protected override void Render( HtmlTextWriter writer )
{

	this.crmGrid.Parameters.Set("qid", Request.QueryString["qid"]);
	base.Render(writer);
}

</script>

I really hoped it would work, but it turns out CRM 4.0 validates querystring parameters. That can be remedied by adding a DWORD to the registry:
http://www.eggheadcafe.com/software/aspnet/31131911/crm-parameter-filter--in.aspx
(Add at HKEY_Local_Machine\Software\Microsoft\MSCRM - DWORD DisableParameterFilter = 1)

Now I can go to http://server/organization/workplace/home_workplace_parameter.aspx?qid={xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

There are some script problems and other stuff I'd like to fix, but for now this works good enough. :)

Posted 27. januar 2009 21:51 by lea | with no comments
Filed under:

I've had some fun with Python.Net today. I'm sure we can find some use for python scripting some time.

Stepped over a few stones to get a working example running, here's a couple of points:

- import CLR has to be stated to interact with .Net code
- Strings passed to .Net has to be unicode
- Why print doesn't output to Console.Out is still a mystery

And without further ado, here's a Hello World app with a function, a variable and some .Net calls:

using System;
using Python.Runtime;

namespace HelloWorld
{
	public class Program
	{
		static void Main(string[] args)
		{
			IntPtr gs;
			PythonEngine.Initialize();
            gs = PythonEngine.AcquireLock();

			try
			{
				int result = 
					PythonEngine.RunSimpleString(""
						+ "import CLR\n"
						+ "from CLR.System import Console\n"
						+ "from time import time,ctime\n"
						+ "def GetHello():\n"
						+ "	return u'Having spam and eggs at ' + unicode(ctime(time()))\n"
						+ "\n"
						+ "hello = GetHello()\n"
						+ "Console.WriteLine(hello)\n"
					);

				Console.WriteLine(result);
				
			}
			finally
			{
				PythonEngine.ReleaseLock(gs);
				PythonEngine.Shutdown();
			}

			Console.ReadKey();
		}
	}
}

Posted 21. desember 2008 01:56 by lea | with no comments
Filed under: , ,

Just had to post this hilarious reply from the IT department.

My screen and the following comments over msn:

Lars-Erik says: (screen now says 40000 days)

ok, done copying in 109 years

Evert says:

I'll notify my grandson

Posted 8. desember 2008 21:19 by lea | 1 comment(s)
Filed under:

I'm following up on that case here, still clinging to some hope:

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=4172128&SiteID=1&mode=1

(This is mostly educational, still under consideration for production use if you should wonder ;) )

Edit 02 jan 2009

Yet another hope stirring:

http://msdn.microsoft.com/en-us/library/bb738631.aspx

Gonna see what happens with the .Cast<>() method if I implement those interfaces on the entities instead of using an expression visitor.

Edit 19 jan 2009

And so he tossed away all the ideas about interfaces for entities as the POCO adapter for L2E arrives :)

http://blogs.msdn.com/jkowalski/archive/2008/09/09/persistence-ignorance-poco-adapter-for-entity-framework-v1.aspx

Thanks to Jimmy Nilsson (http://jimmynilsson.com) for the information.

 

I had the pleasure of getting a rather large pivoted list of numbers to import today, where of course the data to import were niftily marked with a green color.

Of course I have my "convert pivoted stuff to tabluar data" macro ready, but I was unsure how to attack the problem of limiting my list to updated stuff (green rows).

Luckily my mind was quicker than my need to do heavy lifting, so I came up with this nice little custom function:

Public Function GetColor(ref As Range) As String
    Dim color As String
    color = Hex(ref.Cells(1, 1).Interior.color)
    GetColor = color & Replace(Space(6 - Len(color)), " ", "0")
End Function

With it, I could add a column with the following formula:

=GetColor(Table1[[#This Row];[ColoredColumn]])

Et voilá, out comes a nicely formatted hexadecimal color for me to filter on:

Some data 50D092
Some unformatted data FFFFFF
More formatted 50D092
Yellow data FFFF00
And some red data FF0000

If you haven't written custom Excel functions before, the trick is to put them in a module in the VBA project. Excel does not look for custom functions in ThisWorkbook or the Sheetn classes.

Of course, later in the day I found that filtering by color is a new feature in 2007. :P
http://msdn.microsoft.com/en-us/library/cc952296.aspx

 

Posted 28. oktober 2008 23:42 by lea | with no comments
Filed under: ,

I thought of this the other day and wrote a post about it, but my browser crashed when I hit "publish". Might have been a cosmic hint to think it through, but here goes again. :P

To fix a long demanded requirement of uploading multiple files at once to our CMS tool, we finally came out with a Silverlight 2 control handling the issue. (Ended up rather nice :) )
When I copied the .xap to a web app I was gonna use it in, put in a reference to the silverlight dll and referenced the assembly in my user control, the IDE promptly told me to include a scriptmanager too. All right, I thought, that's reasonable.. But when I opened the toolbox to drag one out, I had to find it under the "AJAX" tab.

Whatever happened to good old DHTML?

There's obviously no asynchronous operations when scripting creation of an object tag clientside, and there's not much XML involved either (the contents of the .xap aside). So that leaves us with some HTML and some JavaScript modding the DOM.
In the old days, that was DHTML.
In the old days, I wrote AJAX without knowing it was AJAX using the Remote Scripting Java applet included in Visual InterDev 6.0. I even used Netscape and Access, but I was a devil with DHTML.

Can we please put the hats where they ought to be again? ;)

Posted 17. oktober 2008 06:41 by lea | with no comments
Filed under: , , ,

We got an urgent request today to help a client wrap an internal web application in a custom FireFox browser with an accompagnying .msi installer.

We started hacking along on a fresh 3.0.3 setup package, modding userchrome.css to remove menu items and installing this and that extension. When I was half green in the face and complained loudly to a colleague who hadn't listened to us, he just lit up and uttered "Prism" with a big grin. Saved my day! It's a side project of Mozilla for a light-weight FF installation without any toolbars or other browser clutter. It just shows the web app, and that's it.

(Who at the FF team figured the help menu should be named #helpMenu, when file, edit etc. are #file-menu? And why is the history menu named #go-menu, when all other are equal? God bless Prism...)

We didn't accomplish the .msi installer, but at least we managed to produce a nice small installer using Nullsofts NSIS. Mostly because I found a more or less finished NSIS script for Prism, but also because it's open source and free. And it supported silent install, and passed the customer's policy. :P

All in all, I'm rather happy about the result, and the customer seems rather happy too. :)

Here's the resources:
Prism: http://labs.mozilla.com/projects/prism/
NSIS: http://nsis.sourceforge.net/Main_Page
NSIS Script (bottom of page): http://labs.mozilla.com/forum/comments.php?DiscussionID=285
(Thanks to "royce" https://labs.mozilla.com/forum/account.php?u=1963)

Shortened down and line-break fixed NSIS script:

;NSIS Modern User Interface 

;-------------------------------- 
; Start 

!include "MUI2.nsh" 

!define MUI_PRODUCT "Name of webapp" 
!define MUI_VERSION "" 
!define MUI_BRANDINGTEXT "Your company here" 
CRCCheck On 

;!include "${NSISDIR}\Contrib\Modern UI\System.nsh" 

;-------------------------------- 
;General 

;Name and file 
Name "Name of webapp" 
OutFile "setup.exe" 

;Default installation folder 
InstallDir "$PROGRAMFILES\Prism" 

;Get installation folder from registry if available 
InstallDirRegKey HKCU "Software\Prism" "" 

;Request application privileges for Windows Vista 
RequestExecutionLevel user 

;-------------------------------- 
;Interface Settings 

!define MUI_ABORTWARNING 
!define MUI_ICON "---PATH TO SETUP ICON HERE---"; There's some in the NSIS folder
!define MUI_UNICON "---PATH TO SETUP ICON HERE---"
!define MUI_WELCOMEPAGE   
!define MUI_UNINSTALLER 
!define MUI_UNCONFIRMPAGE 
!define MUI_FINISHPAGE   

;Pages 

!insertmacro MUI_PAGE_WELCOME 
!insertmacro MUI_PAGE_INSTFILES 

# These indented statements modify settings for MUI_PAGE_FINISH 
!define MUI_FINISHPAGE_NOAUTOCLOSE 
!define MUI_FINISHPAGE_RUN 
!define MUI_FINISHPAGE_RUN_CHECKED 
!define MUI_FINISHPAGE_RUN_TEXT "Start -- YOUR APP NAME HERE --" 
!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink" 

!insertmacro MUI_PAGE_FINISH		 


;-------------------------------- 
;Languages 

!insertmacro MUI_LANGUAGE "English" 

;--------------------------------  

;-------------------------------- 
;Installer Sections 

Section "Install Section" SecInstall 

;  set the context for $SMPROGRAMS and other shell folder to the 'current' user 
SetShellVarContext current 

;  PRISM webApp data is recursively copied from yourAppAppData to "$APPDATA\WebApps" 
SetOutPath "$APPDATA\WebApps\-- YOUR PRISM APP NAME HERE --@prism.app\" 
File /r --- YOUR APPDATA FOLDER HERE --- \WebApps\-- YOUR PRISM APP NAME HERE --@prism.app\* 

;  PRISM application data is recursively copied from yourAppPgmFiles to "$APPDATA\yourApp\Prism" 
SetOutPath "$PROGRAMFILES\Prism" 
File /r C:\Program Files\Prism\*   

;windirExists: 
;Store installation folder 
WriteRegStr HKCU "Software\Prism" "" $INSTDIR 


;Create Shortcut links 
CreateShortcut "$DESKTOP\${MUI_PRODUCT}.lnk" "$PROGRAMFILES\Prism\prism.exe" "-override $\"$APPDATA\WebApps\-- YOUR PRISM APP NAME --@prism.app\override.ini$\" -webapp -- YOUR PRISM APP NAME --@prism.app" "$APPDATA\WebApps\-- YOUR PRISM APP NAME --@prism.app\icons\default\webapp.ico"

;Create uninstaller 
;write uninstall information to the registry 
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${MUI_PRODUCT}" "DisplayName" "${MUI_PRODUCT} (remove only)" 
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${MUI_PRODUCT}" "UninstallString" "$INSTDIR\Uninstall.exe"  
WriteUninstaller "$INSTDIR\Uninstall.exe" 


SectionEnd 

;-------------------------------- 
;Uninstaller Section 

Section "Uninstall" 

;  set the context for $SMPROGRAMS and other shell folder to the 'current' user 
SetShellVarContext current 

;ADD YOUR OWN FILES HERE... 

Delete "$DESKTOP\${MUI_PRODUCT}.lnk"  
Delete "$INSTDIR\Uninstall.exe" 

;Delete Files  
RMDir /r "$INSTDIR\*.*"   

RMDir "$INSTDIR"   
RMDir "$PROGRAMFILES\Prism" 
RmDir "$APPDATA\WebApps\-- YOUR PRISM APP NAME --@prism.app" 

;Delete Uninstaller And Unistall Registry Entries 
DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\${MUI_PRODUCT}" 
DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${MUI_PRODUCT}"   
DeleteRegKey /ifempty HKCU "Software\Prism" 

SectionEnd 


Function LaunchLink 
SetShellVarContext current	 

ExecShell "" "$DESKTOP\${MUI_PRODUCT}.lnk" 
FunctionEnd 

[Edit: The problem of eager-loading the stuff I ask for in expand is solved]

I didn't remember when first writing the code and this post that I've got a DataLoadOptions override in a custom context wrapped around the original Linq to SQL model. The Catalog/Items association in the example below is never eager-loaded, so of course I have to add a LoadWith(catalog => catalog.Items) option on the innermost context's DataLoadOptions. Now my .Expand("Items") call gives me a larger set of data and I'm most happy. Just feeling quite stupid not knowing my own API. :P

The code now looks like the attractive:

DataServiceQuery<Catalog> catalogQuery = context.Catalogs
    .Expand(
"Items/ItemFields/TemplateField")
    .Where(c => c.ParentId == (
int)rootCatalogId) as DataServiceQuery<Catalog>;

catalogQuery.BeginExecute(
   
delegate(IAsyncResult result)
    {
        catalogs = catalogQuery.EndExecute(result).ToList();
        AllDone();
    },
null);

The original solution was quite fun to write tho:

[Warning: See bolded text below]

I've been fiddling a bit with an ADO Data Service and the expand method. Can't seem get it to work, or I don't get to know when the associations are loaded.

So I started looking into LoadProperty, but Silverlight wants to call everything asynchronously, so if I loop in the callbacks, I just generate a lot more callbacks, and never know when the last one occurs.

Enter the NotifyingStack. :P

	public class NotifyingStack<T> : Stack<T>
	{
		public event EventHandler OnPopped;

		public NotifyingStack(EventHandler popped) : base()
		{
			OnPopped = popped;
		}

		public new T Pop()
		{
			T obj = base.Pop();
			if (OnPopped != null)
				OnPopped(this, EventArgs.Empty);
			return obj;
		}
	}

Now I can do this:

		private void PopulateSubCatalogsWithItems(object rootCatalogId)
		{
			DataServiceQuery<Catalog> catalogQuery = context.Catalogs
				.Expand("Items/ItemFields")
				.Where(c => c.ParentId == (int)rootCatalogId) as DataServiceQuery<Catalog>;

			justAStack = new NotifyingStack<object>(new EventHandler(OnPop));
			justAStack.Push(null);
			catalogQuery.BeginExecute(
				delegate(IAsyncResult catalogQueryResult)
				{
					IEnumerable<Catalog> catalogResult = catalogQuery.EndExecute(catalogQueryResult);
					catalogs = catalogResult.ToList();
					foreach (Catalog catalog in catalogs)
					{
						justAStack.Push(null);
						context.BeginLoadProperty(catalog, "Items",
							delegate(IAsyncResult itemQueryResult)
							{
								IEnumerable<Item> items = syzwebContext.EndLoadProperty(itemQueryResult).Cast<Item>();
								foreach (Item item in items)
								{
									justAStack.Push(null);
									syzwebContext.BeginLoadProperty(item, "ItemFields",
										delegate(IAsyncResult itemFieldResult)
										{
											syzwebContext.EndLoadProperty(itemFieldResult);
											justAStack.Pop();
										}, null);
								}
								justAStack.Pop();
							}, null);
					}
					justAStack.Pop();
				}
				, catalogQuery);
			
		}

		private void OnPop(object sender, EventArgs e)
		{
			System.Diagnostics.Debug.WriteLine(justAStack.Count);
			if (justAStack.Count == 0)
			{
				AllDone();
			}
		}

I was first experimenting with a timer checking a regular stack, but when trying to add stuff to the UserControl, Silverlight threw a cross threading exception.

In this case, the event handler is in the right thread, and as long as I'm diciplined with the pushing and popping, the AllDone method will only be called once when all the data is loaded. :)

Now, if anyone have a good laugh because you know how to get the expand method to instruct the BeginExecute method to get a big package, ie. by altering the server side service, please gimme a comment. (I've been looking at the xml from the ADO Service, and it only generates urls to the data for the expanded properties)

Turns out though, I need a lot more than the stuff I got so far. So I have to keep this little trick up my sleave for something else. This method would create about a 100 small chatty calls to the server before done, hence I'm gonna [Edit: Remember my DataLoadOptions in the future]. :)

(But it was real fun doing it)

 

[Edit: This post is a continuation of this post. The latter contains some links to some of the code used. For instance VarianceWorkaround] 

It didn't actually turn out quite as I had hoped, (the DataServiceQuery don't know how to translate interface queries to URL queries) but at least I'm able to write code such as this:

private void userControl_Loaded(object sender, RoutedEventArgs e)
{
	IQueryableprojectQuery = 
		context.Project
		.Expand("ProjectEntries")
		.Where(p => p.Name.Contains("project"))
		.Skip(1)
		.Take(2);
	context.BeginExecute(newAsyncCallback(GotResult), projectQuery);
}
protected void GotResult(IAsyncResult result) { IList<Model.Project> projects = context.EndExecuteAsList<Model.Project>(result); }

I generated a proxy for an ADO Data Service using the Entity Model created for my previous posts as described here:
http://msdn.microsoft.com/en-us/magazine/cc794279.aspx

Then hooked up all the interfaces on the generated entities and created these two methods on the datacontext:

public void BeginExecute(AsyncCallback callback, IQueryable queryable)
{
    queryable.GetType().GetMethod(
"BeginExecute").Invoke(queryable,
        new object[] { callback, queryable });
}


public IList<T> EndExecuteAsList<T>(IAsyncResult result) where T : Model.OPWObject
{
    DataServiceQuery query = result.AsyncState as System.Data.Services.Client.DataServiceQuery;
    Type elementType = query.ElementType;
    System.Reflection.
MethodInfo toListGenericMethod = typeof(Enumerable).GetMethod("ToList")
        .MakeGenericMethod(
new Type[] { elementType });
    System.Data.Services.Client.
QueryOperationResponse queryResponse =
        query.GetType().GetMethod(
"EndExecute").Invoke(result.AsyncState, new object[] { result })
        as
System.Data.Services.Client.QueryOperationResponse;
    System.Collections.
IList list = toListGenericMethod.Invoke(null, new object[] { queryResponse })
        as System.Collections.IList;

    System.Reflection.MethodInfo listConvertMethod = null;
    foreach (System.Reflection.MethodInfo method in typeof(VarianceWorkaround).GetMethods())
        if (method.ReturnType.Name == "IList`1")
        {
            listConvertMethod = method;
            break;
        }

    System.Reflection.MethodInfo genericListConvert = listConvertMethod.MakeGenericMethod(
        new Type[] { elementType, typeof(T) }
    );

    IList<T> retVal = genericListConvert.Invoke(null, new object[] { list }) as IList<T>;
    return retVal;
}

I believe my main concern is to use lists of the interfaces in my UI logic. So as long as I can rely on intellitip in the queries without referencing the "real" type, I'm where I want to be. :)

Of course I need to have some kind of factory method to create instances of the entities, but that's a small sacrifice.

I do wonder though; as I discovered the EndExecute method of the context itself and not just on the DataServiceQueries when I was done; whether my code could be considerably shorter...

[Edit: Once again, I revisit this one, and it's probably still flawed as h***]

I guess the VarianceWorkaround posted here will give you the complete table contents before filtering based on the interface properties. At least that's what happens when using L2S.

Some day soon I'll hopefully manage to create either an ExpressionTreeVisitor to translate, or find or be told a better solution.

 

[Edit: I leave my original ramblings without the accusations.. see my solution further down. :) ] 

I was almost there yesterday, but when I finnaly got all the lines right and ran my test, [Edit]I had probably sat for too long[/Edit].

The following attempt at a repository for ADO.NET Entities throws a sad exception. Anyone?

public IQueryable<T> Repository<T>() where T : class, global::MyNamespace.IEntityInterface
{
   
EdmType theType =
        types.Where(
             t =>
Type.GetType(t.FullName).GetInterface(typeof(T).FullName) != null
        ).FirstOrDefault();
    
Type entityType = Type.GetType(theType.FullName);
   
MethodInfo createQuery = internalContext.GetType().GetMethod("CreateQuery").MakeGenericMethod(entityType);
   
object[] parameters = new object[] { theType.Name, new System.Data.Objects.ObjectParameter[0] };
   
IQueryable queryable = createQuery.Invoke(internalContext, parameters) as IQueryable;
   
if (queryable != null)
       
return ((IQueryable)queryable).Cast<T>(); // Throws the baddie
   
else
       
return null;
}

Throws the following:

System.NotSupportedException: Unable to cast the type 'EntityImpl' to type 'IEntityInterface'. LINQ to Entities only supports casting Entity Data Model primitive types..

[Edit: And here's how my week-end might turn out to include time with my better half after all... :P]

Turns out I'd already posted the answer to my casting woes in my original post. The generics variance wrapper can at least convert enumerables of implemetations to their respective interfaces. Of course this demands dicipline when using (not casting birds as seagulls etc.), but at least I was able to query against the interface. And that's a huge step forward!

I'm gonna dive into the VarianceWorkaround class sometime and see if I can't make it fix IQueryables too.

Here's the working repository method:
(If anyone has any tips on how to get rid of the reflection mess, feel free to come forward)

public IEnumerable<T> Repository<T>() where T : global::IEntity
{
   
// LTE type for source type
    EdmType theType = 
        types.Where(
            t =>
Type.GetType(t.FullName).GetInterface(typeof(T).FullName) != null
       
).FirstOrDefault();

    // Source type
   
Type entityType = Type.GetType(theType.FullName);

    // Get create query method
   
MethodInfo createQuery = internalContext.GetType().GetMethod("CreateQuery").MakeGenericMethod(entityType);
    object[] parameters = new object[] { theType.Name, new System.Data.Objects.ObjectParameter[0] };

    // Get queryable
   
IQueryable queryable = createQuery.Invoke(internalContext, parameters) as IQueryable;
   
if (queryable != null)
    {
       
// source
       
Type enumerableType = Type.GetType("System.Collections.Generic.IEnumerable`1");

       
// Find enumerable override of convert (find better way..?)
        MethodInfo enumerableConvert = null;
       
foreach(MethodInfo method in typeof(VarianceWorkaround).GetMethods())
           
if (method.ReturnType.Name == enumerableType.Name)
            {
                enumerableConvert = method;
                
break;
            }

        // Get typed version of the convert method
       
MethodInfo genericEnumerableConvert = enumerableConvert.MakeGenericMethod(
           
new Type[] { entityType, typeof(T) }
        );
        // Cast the objectquery as IEnumerable<T>
       
IEnumerable<T> retVal = genericEnumerableConvert.Invoke(null, new object[] { queryable }) as IEnumerable<T>;

        return retVal;
    }
    else
       
return null;
}

More Posts Next page »