|
|
|
|
 Thursday, July 16, 2009
|
I came across a thread on hubFS about deserialising F# records with JSON. After Mr. McNamara pointed out that DataContract serialization would work with F# records, I realised we can do the same for other serialisation systems, such as ASP.NET MVC.
ASP.NET MVC binding doesn't work with F# records for a few reasons. First, it requires a default constructor, and record types don't have one. Second, it needs settable properties, and records have read-only properties. Fortunately, the backing field for a record's property is a mutable field. The name is mangled (@ is appended), but otherwise we're ok to set that field.
With this, we can subclass the default model binder and add in code to construct records as well as set their fields directly. Unlike DataContract serializers, I didn't use FormatterServices.GetUninitializedObject to create the object, I use the F# reflection function MakeRecord. This is because I want to attempt to initialise all fields on the record type, to try to keep out nulls. This goes against how the rest of MVC's null handling goes, so perhaps it's not a great idea.
At any rate, here's the quite short code. A lot of things probably don't work, such as F# lists. Perhaps there should be a community project that collects F#-specific type helpers for different frameworks to make serialization, binding, etc. easier.
open System
open System.Web.Mvc
open Microsoft.FSharp.Reflection
type RecordDefaultModelBinder() =
inherit DefaultModelBinder()
let isrec = FSharpType.IsRecord
/// Makes a record, trying to provide initialised values for each field
let rec makeDefaultRecord ty =
let defval ty =
if isrec ty then makeDefaultRecord ty
else match ty.GetConstructor(Type.EmptyTypes) with null -> null | c -> c.Invoke null
let vals = FSharpType.GetRecordFields ty |> Array.map (fun x -> defval x.PropertyType)
FSharpValue.MakeRecord(ty, vals)
override this.CreateModel(cc, bc, ty) =
// We have to avoid them calling Activator.CreateInstance on records
if isrec ty then makeDefaultRecord(ty) else base.CreateModel(cc, bc, ty)
override this.GetModelProperties(cc, bc) =
// Default one filters out read-only, but we own the field
if isrec bc.ModelType then
let props = ComponentModel.TypeDescriptor.GetProperties(bc.ModelType)
|> Seq.cast<ComponentModel.PropertyDescriptor> // BCLFail
|> Seq.filter(fun p -> bc.PropertyFilter.Invoke(p.Name))
ComponentModel.PropertyDescriptorCollection(Seq.to_array props)
else
base.GetModelProperties(cc, bc)
override this.SetProperty(cc, bc, propDesc, value) =
// To set a record property, set the mangled field
if isrec bc.ModelType then
let field = bc.ModelType.GetField(propDesc.Name + "@", Reflection.BindingFlags.Instance ||| Reflection.BindingFlags.NonPublic)
field.SetValue(bc.Model, value)
else
base.SetProperty(cc, bc, propDesc, value)
|
|
ASP.NET | FSharp
|
Thursday, July 16, 2009 9:35:53 AM UTC
|
Trackback
|
 Friday, May 15, 2009
|
If you use ASP.NET MVC with F# CTP (Monday brings Beta 1 and probably many changes), you might run into an issue of how to scope certain things. For example, an IDisposable is used to create blocks, for things like an HTML form. Example in C#:
<% using (Html.BeginForm()) {%>
<fieldset><legend>Fields</legend>...</fieldset>
<% } %>
This is needed because VB and C# didn't have any easy function/block syntax (VB 10 should fix this), and many C# developers are still wary of higher order functions. How does this play out in F#? First off, whitespace is important. This can get really messy with the current F# ASP.NET integration. Basically, always open the script blocks on a separate line, and indent from the first column. Example:
<% let i = 0
this.Response.Write(sprintf "i = %d" i) %>
(Note that the 'this' variable is bound to the current page.) This fails:
Compiler Error Message: FS0010: Unexpected keyword 'let' or 'use' in expression. Expected incomplete structured construct at or before this point or other token
Source Error:
|
Line 111: let mutable parameterContainer = parameterContainer
Line 112: __w.Write("\r\n") |> ignore
Line 113: let i = 0
Line 114: this.Response.Write(sprintf "i = %d" i)
Line 115: __w.Write("\r\n <h2>Create</h2>\r\n\r\n ") |> ignore |
Note how the <% is counted as space, so the let starts off indented 3 spaces. Instead, we need to write it so:
<%
let i = 0
this.Response.Write(sprintf "i = %d" i) %>
This works fine. You can also put the %> on the next line if you like. Now, on to ASP.NET MVC's IDisposable usage. A straightforward use of the F# using function won't work:
<b>Demo</b>
<%
using (Mvc.Html.FormExtensions.BeginForm this.Html) (fun _ -> %>
<p>Inside a form</p>
<% ) %>
Compiler Error Message: FS0191: The mutable variable '__w' is used in an invalid way. Mutable variables may not be captured by closures. Consider eliminating this use of mutation or using a heap-allocated mutable reference cell via 'ref' and '!'.
Source Error:
|
Line 114: __w.Write("\r\n\r\n<b>Demo</b>\r\n\r\n") |> ignore
Line 115:
Line 116: using (Mvc.Html.FormExtensions.BeginForm this.Html) (fun _ ->
Line 117: __w.Write("\r\n\r\n <p>Inside a form</p>\r\n\r\n") |> ignore
Line 118: ) |
As the error says, this is because the __w variable is mutable, so we can't play with it inside a lambda. I'm not sure if this will be worked around -- they'd have to change the codegen quite a bit, I'd think. As a side note, the F# CTP does not support C# extension methods (hence the verbose calling of BeginForm), but F# will eventually -- maybe in the Beta.
The way we must scope is with a use binding. There's no way I see to accomplish this with whitespace alone. Due to the ASPX translation process, this would probably be very error prone. Instead, we can simply put the use binding inside a do expression:
<b>Demo</b>
<%
do (use form = Mvc.Html.FormExtensions.BeginForm this.Html %>
<b>Inside the form</b>
<% ) %>
<b>Outside of the form</b>
The parentheses setup the scope exactly how we want it.
|
|
ASP.NET | FSharp
|
Friday, May 15, 2009 8:46:39 PM UTC
|
Trackback
|
 Wednesday, August 27, 2008
|
[Yea, I’m not a web dev, and actively avoid it as much as
possible, so I’m late to this party.]
The interesting thing about ASP.NET MVC is that it takes an
opposite approach to ASP.NET in general. ASP.NET concepts try to “build up”, so
as to shelter us from the evolved idiocy that is HTML, as well as clean up the
inherently stateless nature of HTTP. ASP.NET MVC makes no attempt and forces
you to deal with reality. Considering ASP.NET’s abstraction isn’t really
perfect (example: databinding sucks), MVC’s approach is unfortunately
refreshing.
Because of its “raw” nature, you’ll be writing a lot more
HTML than you’d do with ASP.NET, and this HTML must line up with code on your
server. To make this less of a pain, there are some HTML helper functions. Rob “
type inference” Conery has an overview here.
Here’s the signature for one of the functions:
public static string CheckBox(this HtmlHelper helper,
string htmlName, string text, string value, bool isChecked, object
htmlAttributes);
The last parameter confused me. Why would it be an object?
Am I supposed to pass in an IDictionary<string,string>? Just a long
string? To make it more confusing, other helpers had two overloads:
public static string TextArea(this HtmlHelper helper,
string htmlName, object value, IDictionary<string, object> htmlAttributes);
public static string TextArea(this HtmlHelper helper,
string htmlName, object value, object htmlAttributes);
OK, so they explicitly called out the IDictionary there – THEN
WHO WAS OBJECT HTMLATTRIBUTES?
Rob covers in his overview. The idea is that you’re supposed
to use anonymous types to hack around the lack of tuples.
<%=Html.Whatever(arg1, bla, …, new { @class=”cssx”,
style=”x:f” …}) %>
What a great case of not-having-built-in-tuples-is-lame. It’s
so lame, Microsoft’s own developer teams have to resort to weird (but quite
creative!) hacks like this so that their syntax won’t completely suck*. Damn.
And now, a duck
For bonus points, there is another C# compiler feature that the MVC team could
have [ab]used, and it would arguably have made more sense (although the syntax
isn’t as tight). C# supports duck typing on collection initializers! So, they
could create a class like this:
public class HtmlAttributes : System.Collections.IEnumerable
{
public System.Collections.IEnumerator
GetEnumerator() { ... }
public void Add(string name, object
val) { ... }
}
And then they can write this:
new HtmlAttributes
{ { "A", 123} , {"B", "test"}
}
No, it’s not as tight as the anonymous type syntax, but it
does make a lot more sense. And tuples still make much more sense than
either approach, and have benefits for the rest of the language to boot (death to out parameters!).
*The only benefit I see in anonymous types is that, at compile time, you'll
know there are no key conflicts - but that is totally trivial in the way they use them, since all the keys are declared right there.
|
|
ASP.NET | Code
|
Wednesday, August 27, 2008 1:12:13 AM UTC
|
Trackback
|
 Tuesday, August 26, 2008
Someone on our team started using ASP.NET MVC for a new web interface we're doing. I must say I'm impressed with the level that the MVC team [ab]uses the C# compiler, mostly in a good way. On the plus side, they end up with a bit more compiler time checking than would be possible otherwise (we can only hope WPF will follow suit some day).
But one thing struck me odd was how their helper method for generating an HTML form works. The goal is to generate an HTML form tag with the right action, and they use lambdas as symbolic references to figure out the action. The next problem is making sure that the <form> gets closed with a </form>. The straightforward answer to this is "create a function that takes a function". The C# signature would be: void Form<A>(Expression<Action<A>>, Action). Then your ASPX code would be:
<% Html.Form<FooController>(x => x.Edit(someVar.FieldX), () => { %> Some Html <% SomeCode%> <% }); %>
The code is nicely bracketed and works fine. But ASP.NET MVC doesn't actually do that. Instead, the Form method returns an IDisposable! The code to use it is:
<% using (Html.Form<FooController>(x => x.Edit(someVar.FieldX)) { %> Some Html <% SomeCode%> <% } %>
Why do they use an IDisposable? The rest of the MVC framework seems to assumes people are somewhat familiar with lambdas, closures and what not. The only thing I can think of is that VB doesn't support anonymous methods. So in order to make it VB friendly, they come up with quite a strange use of IDisposable to abuse language support for it. Overall, I'm not sure if this is dumb or cute.
|
|
Code | ASP.NET
|
Tuesday, August 26, 2008 9:19:59 PM UTC
|
Trackback
|
|
|