asp.net mvc as more alphabet soup ? plus swipes at youth, twitter, and .. hey, it's a rant, ok ?
here we go again...
- craig:
- So you think the MVC pattern is something worth looking at closer? I read about it a few months ago in an MSDN magazine article. It did look pretty cool in that the view can be easily abstracted as a win form, web page or silverlight control since it's communicates with the controller through events. Let me know what you think, I'm interested. ...
- me:
-
well, that chapter is a quick read, and it seems to be enough to give an accurate first impression. now, the quality of that first impression may vary. the chapter's at http://tinyurl/aspnetmvc, book name on front cover.
the sample is for a web app only. this is asp.net mvc, not a general mvc framework. not sure how this might map, if at all, to other presentation methods / runtime contexts. maybe there's more info in the more in-depth chapters of that book. it's not out yet, and that chapter is in pre-publication shape too, has some noticeable (minor) errors.
the sample app is live at nerddinner.com. now, on my xo's browser, it doesn't come up well at all - missing all kinds of styles, the maps, much navigation, etc.. not sure what that's all about, probably ajax related, on which this particular app seems to rely heavily in some areas. or perhaps the xo's browser is not jquery friendly, who knows. not worth finding out. but it's a mozilla xulrunner / gecko core, so it's interesting that this thing doesn't work there.
asp.net mvc v1.0 - we all know about ms first versions. in the sample app, they hand craft data listing and paging logic. now, it's a lot easier than it would have been pre c# 2.0, but one would think that all the tools (e.g., server controls) that we've come to rely on in webforms would need functional representation in this framework as well. anyone doing a not-small website will end up having to build or borrow a library of support functionality. the existing server controls aren't a good match for this framework.
mvc itself - eh, ok, this is stuff we should at least be aware of. the asp.net version seems not-ready-for-prime-time, but it certainly can support building small personal sites. but as for the "pattern" - if I ponder my own code habits over the years, I can see that I kind of do a lot of these things in one form or another already. it's a less overt, often less clean, but sometimes more elegantly subtle approach containing these same ideas. and that makes sense that halfway decent coding practices would converge on similar solutions to similar problems. with variation, of course. I noticed that the other day when considering what I had just done in that twitter-to-communityserver aggregator I've been tinkering with. it's got all the same types of layers, and when I dug up a c# new language features list, I discovered that I was already using some of that stuff without being aware of it. some things just seem "natural" to try. the good news is that these natural impulses now are often successful, whereas they used to result in failures, just brick walls with "I wish I could do this" written on them. for example, I got away with using an unnamed delegate, passing an expression that returned a method signature as a parameter on a constructor. they call that sort of thing "dependency injection" and "inversion of control" these days. same goes for using reflection to instantiate types specified in configuration files, for example. like with "ajax" the terminology and hype is often obscuring rather than reinforcing solid technical habits.
which is disturbing. it does two things: (1) it disenfranchises existing practitioners to some extent, similar to the new slang on the playground every year; and (2) it encourages unthinking formulaic approaches to this profession, which as a class has never been successful. great - another golden hammer, which the young'uns - and wanna-be trend-chasing old'uns - declare to be the "right" way. apparently perspective, experience and wisdom are still not valued. nothing new there.
so, mvc, ruby on rails, dynamic languages, closures, lambda functions, ioc, patterns, etc. etc. etc. - been there, done that, quit yanking me around with the labels. pisses me off that all those morons need a goddamn popular fad and a catchy name before they'll listen to any sense - assuming that there's a correspondence to sense there in the first place, which isn't always the case.
blogging, I know.
so, I'm tiring of twitter. another fad. it's nice to have that micro-blogging channel (in the strict sense), as I posted about earlier, but fuck using it as a chat / im mechanism. if I want to talk _to_ someone, I'll do it via better channels. I was never really into talking loudly at parties. who are we trying to impress here ? and as for the information sharing, there are better, more focused ways - delicious, blogs, whatever. as a news channel ? not hardly. the only motivation I can see that would keep me hanging around is that "everyone else is doing it", and not because of wanting to be there - it's that people have disappeared from those other channels. so I have to choose between going my own way, and joining the rest of the world in a least common denominator fuck fest ? I'll choose the way I always do, thank you. meanwhile, I'll micro-blog in a vaccum, the way I regular blog. it's just a tool.
...
oh - no "events" concept in asp.net mvc. get used to it ;-p
Wednesday, March 25, 2009
-
2009.03.25 05:10:
c & .net features by version http://is.gd/oPsk - good list of links, references; wish for 4.0 to be added
-
2009.03.26 01:02:
_custom_ shortened urls at tinyurl, bit.ly & moourl - a hijacker's dream. since last july. grab yours while you can.
what you _don't_ see is what you get: digging into the details of .net reflector's forced auto-update "feature".
one little annoyance - and suspicion - in the .net development world is the behavior of .net reflector when it comes to updating itself. there's no way around it. and if you're not careful, the thing will even delete itself in some circumstances. that behavior is suspiciously close to being a solid example of stallman's "treacherous computing" criticism.
phone home
using the current version, go to "Help -> Check for Updates". fiddler shows that Reflector connects to location http://reflector.red-gate.com/reflector.version, sending the request:
GET /Reflector.version HTTP/1.1
Cache-Control: no-cache, no-store, max-age=0<
Host: reflector.red-gate.com
Proxy-Connection: Keep-Alive
and getting the response:
HTTP/1.1 200 OK
Content-Length: 63
Content-Type: text/plain
Last-Modified: Fri, 17 Oct 2008 09:34:07 GMT
Accept-Ranges: bytes
ETag: "a4fb7b803b30c91:5fd"
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
Date: Fri, 20 Mar 2009 20:08:16 GMT
5.1.4.0
4.0.0.0
http://downloads.red-gate.com/reflector.zip
setting up a fiddler auto-response for the request like this seems to work. better would be to set up a dns entry somewhere rerouting that specific url to a (local) http proxy that does the same. could even do this so that reflector versions across an organization or project pull from the same update cache - under the control of the consumer.
ok, so that satisfies a bit of paranoia regarding what the call-home exchange may have been. why isn't it documented more transparently ? and still, that's not really acceptable behavior, for any reason.
delete me
next, need to install & run an old version to see what attempting to fire that up does. i've got one hanging around from a couple of years ago, dated 2006.12.10, v4.2.51.0.
trying to fire it up results in:

select "no", and i get this:

reflector.exe was marked read only, so it remained.
running it again and responding "yes" to the update question, it reaches for http://www.aisto.com/roeder/dotnet/Reflector.version. if it can't find that for some reason (say, from being offline), it displays an error message:
and proceeds to (attempt to) delete the executable as before.
running again, responding "yes", and letting it find the (faked) response as above, it reaches for the download reference in the response. failing here, it also attempts to delete the executable.
now, before going further, if i change the first number version in the response to be the same as the one i'm trying to run, the update dialog is still presented, and returns immediately with

but - after closing this dialog, the deletion attempt is still made. yeah, it's time-bombed from the inside alright. i even tried feeding it the old version zip as the download reference in the faked response - that just loops the deletion / update process; good if you're in the mood for some strange sort of self-abuse.
the only way around that deletion that anyone seems to have found is to change the system date before starting the app. i haven't tried, but it is probably possible to set up a cmd file to do the date change in a way reflector will recognize, launch & change back.
since reflector's internals are well protected, it seems the most practical approach is to hope redgate listens to the folks on its forums about this.
just ugly.
first refactoring of my twitter to blog post aggregator (code).
it usually takes me three rounds of coding to get something somewhat satisfactory - the original "just get it to work" phase, and two refactorings. the first refactoring follows, with some notes in the comments for the next round:
- fractalnavel.CS.PostTwitter.cs
using System;
using System.Xml;
using CommunityServer.Blogs.Components;
using CookComputing.XmlRpc;
using fractalnavel.CS.components;
namespace fractalnavel.CS
{
class CMain
{
[STAThread]
static void Main(string[] args)
{
XmlDocument xmlSettings = new XmlDocument();
xmlSettings.Load( args[0] );
GetAndPost gap = new GetAndPost(
xmlSettings.DocumentElement,
(XmlRpcProxyGen.Create<IMetaWebLogNewPost>()).newPost
);
if ( gap.Get() )
gap.Post();
else if ( gap.PostNone )
gap.Post( "nothin'" );
}
}
[XmlRpcUrl("*** wherever it was put *** /metablog.ashx")]
public interface IMetaWebLogNewPost
{
[XmlRpcMethod("metaWeblog.newPost")]
string newPost(
string blogid,
string username,
string password,
MetaWeblog.Post post,
bool publish
);
}
}
- fractalnavel.CS.components.cs
using System;
using System.Xml;
using System.Xml.Serialization;
using CommunityServer.Blogs.Components;
using fractalnavel.components;
namespace fractalnavel.CS.components
{
public class GetAndPost
{
private MakeNewPost _doPost;
private Settings _settings;
private XmlDocument _xmldocAll = new XmlDocument();
private string _strPostBody = string.Empty;
public delegate string MakeNewPost(
string blogid,
string username,
string password,
MetaWeblog.Post post,
bool publish
);
public GetAndPost( XmlNode nodSettings )
{
GetAndPost( nodSettings, (new MetaWeblog()).newPost );
}
public GetAndPost( XmlNode nodSettings, MakeNewPost DoPost )
{
_doPost = DoPost;
_settings = (Settings) XmlUtils.Deserialize <Settings> ( nodSettings );
}
public bool PostNone {
get{ return _settings.bPostNone; }
}
public string PostBody
{
set {_strPostBody = value;}
get
{
if ( _strPostBody == string.Empty )
_strPostBody = XmlUtils.Transform( _xmldocAll, _settings.hrefXsl );
return _strPostBody;
}
}
public bool Get()
{
string href = _settings.hrefGet + "?count=200&since=" + ((DateTime.UtcNow).AddHours( - _settings.hours )).ToString("r");
_xmldocAll.LoadXml( XmlUtils.GetTextFromHref( href, _settings.useridGet, _settings.pwdGet ) );
return (_xmldocAll.SelectNodes("//status")).Count > 0;
}
public string Post()
{
return Post( this.PostBody );
}
public string Post( string strPostBody )
{
bool bPublish = true;
DateTime dtNow = DateTime.Now;
MetaWeblog.Post post = new MetaWeblog.Post();
post.title = _settings.titleBlog + " " + (dtNow).ToString("yyyy.MM.dd");
post.dateCreated = dtNow;
post.description = strPostBody;
return _doPost( _settings.idBlog, _settings.useridBlog, _settings.pwdBlog, post, bPublish);
}
[Serializable()]
public class Settings
{
private string _hrefGet;
private string _useridGet;
private string _pwdGet;
private string _hrefXsl;
private string _idBlog;
private string _useridBlog;
private string _pwdBlog;
private string _titleBlog;
private int _intHours;
private bool _bPostNone;
public Settings() {}
[XmlAttribute("hrefGet")]
public string hrefGet{get {return this._hrefGet;} set {this._hrefGet = value;}}
[XmlAttribute("useridGet")]
public string useridGet{get {return this._useridGet;} set {this._useridGet = value;}}
[XmlAttribute("pwdGet")]
public string pwdGet{get {return this._pwdGet;} set {this._pwdGet = value;}}
[XmlAttribute("hrefXsl")]
public string hrefXsl{get {return this._hrefXsl;} set {this._hrefXsl = value;}}
[XmlAttribute("idBlog")]
public string idBlog{get {return this._idBlog;} set {this._idBlog = value;}}
[XmlAttribute("useridBlog")]
public string useridBlog{get {return this._useridBlog;} set {this._useridBlog = value;}}
[XmlAttribute("pwdBlog")]
public string pwdBlog{get {return this._pwdBlog;} set {this._pwdBlog = value;}}
[XmlAttribute("titleBlog")]
public string titleBlog{get {return this._titleBlog;} set {this._titleBlog = value;}}
[XmlAttribute("hours")]
public int hours{get {return this._intHours;} set {this._intHours = value;}}
[XmlAttribute("bPostNone")]
public bool bPostNone{get {return this._bPostNone;} set {this._bPostNone = value;}}
}
}
}
- fractalnavel.components.cs
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Xsl;
namespace fractalnavel.components
{
public class XmlUtils
{
public static string GetTextFromHref(string url, string user, string password)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
request.Credentials = new NetworkCredential(user, password);
WebResponse response = request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream());
string responseString = reader.ReadToEnd();
reader.Close();
return responseString;
}
public static string Transform( string strXmlHref, string strXmlUserId, string strXmlPassword, string strXslHref )
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml( GetTextFromHref( strXmlHref, strXmlUserId, strXmlPassword ) );
return Transform( xmlDoc, strXslHref );
}
public static string Transform( XmlDocument xmlDoc, string strXslHref )
{
XslTransform xsl = new XslTransform();
xsl.Load( strXslHref, new XmlUrlResolver() );
StringBuilder sbResult = new StringBuilder();
StringWriter swResult = new StringWriter( sbResult );
xsl.Transform( xmlDoc, null, swResult, null );
return sbResult.ToString();
}
public static Object Deserialize <T> (XmlNode node)
{
XmlSerializer ser = new XmlSerializer( typeof(T) );
return ser.Deserialize(new XmlNodeReader(node));
}
}
}
- Settings.xml
<?xml version="1.0" encoding="utf-8" ?>
<>
<Settings
hrefGet="http://twitter.com/statuses/user_timeline.xml"
useridGet="***"
pwdGet="***"
hrefXsl="*** wherever it was put *** /GetAndPost.xsl"
idBlog="*** destination weblog name ***"
useridBlog="***"
pwdBlog="***"
titleBlog="a day in a life"
hours="24"
bPostNone="false"
/>
- GetAndPost.xsl
<?xml version="1.0" ?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:user="http://blogs.no-ip.org/fractalnavel"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="xsl user msxsl"
>
<xsl:output
method='html'
omit-xml-declaration="yes"
version="1.0"
encoding="UTF-8"
indent="yes"
cdata-section-elements=""
/>
<>
<xsl:template match="/statuses">
<div class="divLTAll">
<div class="divLTHead">
via @<a href="http://www.twitter.com/*** who you are ***" target="_blank"
title="twitter!">*** who you are ***</a>: in the last twenty-four hours:
</div>
<div class="divLTBody">
<ul>
<xsl:apply-templates select="status" >
<xsl:sort select="position()" order="descending" data-type="number"/>
</xsl:apply-templates>
</ul>
</div>
<div class="divLTFoot">
(pulled direct from twitter via custom job)
</div>
</div>
</xsl:template>
<xsl:template match="status">
<li><span class="spnLTItemDate"><xsl:value-of select="user:fnNormalizeDate(string(created_at))" /></span>:
<xsl:value-of select="user:fnSetLinks(string(text))" disable-output-escaping="yes" /></li>
</xsl:template>
<msxsl:script language="JScript" implements-prefix="user">
//<![CDATA[
function fnSetLinks( strText )
{
// embedded hrefs
strText = strText.replace( /(http:\/\/\S*)/g, "<a href=\"$1\" class=\"aLTLink\" target=\"_blank\">$1</a>" );
// twitter ids
strText = strText.replace( /@(\S*)/g, "@<a href=\"http://www.twitter.com/$1\" class=\"aLTUser\" target=\"_blank\">$1</a>" );
// hash tags
strText = strText.replace( /#(\S*)/g, "<a href=\"http://search.twitter.com/search?q=%23$1\" class=\"aLTHash\" target=\"_blank\">$1</a>" );
return strText;
}
function fnNormalizeDate(strDate) {
try {
// "Mon Mar 16 18:16:59 +0000 2009" to "2009.03.16 18:16":
var rxMatch = /.{3} (.{3}) (\d{2}) (\d{2}:\d{2}):\d{2} \+\d{4} (\d{4})/;
strDate = strDate.replace( rxMatch ,
function(strMatch, strMonth, strDay, strTime, strYear)
{
var dat = new Date( strMonth + " " + strDay + " " + strYear + " " + strTime );
// months number is zero based ?!? wtf
strMonth = (dat.getUTCMonth()+1).toString().replace( /^(\d)$/, "0$1" );
return strYear + "." + strMonth + "." + strDay + " " + strTime;
});
} catch(e) {
strDate = e.message;
}
return strDate ;
}
//]]>
</msxsl:script>
</xsl:stylesheet>
i also need to reorganize the naming a bit. i'll reserve commentary until the last revisions are complete.