Wednesday, December 12, 2012
Estimation
is easy: put any random number, just kidding.
Instead,
always answer 1. But, change the meaning
of that 1. Express your estimates in
hours, days, weeks, months, seasons or quarters, years, decades, lifetime.
Humans are
great at these kinds of estimates, and everyone can grasp the “order of
magnitude” of such estimates.
How long
will it take to implement feature x?
1 day has a
completely different implication than 1 year.
1 day (even
if it takes 2 or even 3 days). It means
that technology is there, that the dev team knows what to do and how to do
it. Also, it means that a 100% slip in
schedule equals 1 day, not one year.
1 year says
a lot: you need to cut the feature in smaller chunks, you need a team, there
will holidays, vacations, sick leaves, short leaves, pregnancy, the technology
might not be there yet, there will probably research and development, proof of
concepts, many changes in requirements as the project evolves and the client
plays with the software, etc. etc. In a year, a 1% slip is equivalent to 1 week!
So,
estimate using 1 and an “order of magnitude”.
Thursday, August 16, 2012
RavenDB is an Open Source, document DB (NoSql)
Links:
- CodePlex: http://ravendb.net/
- WikiPedia on NoSql http://en.wikipedia.org/wiki/NoSQL
RavenDB can run as a server (standalone) or embedded in your application.
Here’s a very brief example of embedding RavenDb in an ASP.Net MVC application, it takes about 5-10 minutes in all.
Basic Setup Steps
- Create an empty Asp.Net MVC Web Application
- Install NuGet (if not already done)
- Install RavenDb Embedded (will also install RavenDb Client)
- Modify Global.Asax (this is where you will start the RavenDb embedded server)
- That's it, press F5 to build and run....
Note: You “start” RavenDb (server) in the Global.asax by convenience. Start and Stop of the server is process intensive and you want to do it only once.
Detailed Setup Steps
=> Modify Global.Asax (this is where you will start the RavenDb server)
1) Create a Property
public static DocumentStore Store { get; set; }
2)
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
Store = new EmbeddableDocumentStore { DataDirectory = "~/Store", UseEmbeddedHttpServer = true };
Store.Initialize();
}
Validation
There are two ways to verify the installation:
- through RavenDb's integrated Management Studio (simply navigate your browser to http://localhost:8080)
- or by code…
In this silly example, I'll modify the "HomeController" and the Index page. Note, to fake some initial data to work with, I'll simply override the HomeController's constructor and save a dummy object to RavenDb if none exist...
- Create a New RavenPerson Class (this will be my model)
- Override the constructor and store a sample record (just for the sake of testing)
- Override the Index() method to fetch the sample record and pass it to the view
- Override index.cshtml
- F5
Validation Details
=> Create a New RavenPerson Class (this will be my model)
namespace MvcApplicationForRavenDb.Controllers
{
public class RavenPerson
{
public string Id { get; set; }
public string Name { get; set; }
}
public class HomeController : Controller
{ …
=> Override the constructor and store a sample record (just for the sake of testing)
public HomeController()
{
using (var session = MvcApplication.Store.OpenSession())
{
var rp = session.Load<RavenPerson>("/Person/1");
if (rp == null)
{
rp = new RavenPerson();
rp.Id = "/Person/1";
rp.Name = "Pat";
session.Store(rp,rp.Id);
session.SaveChanges();
}
}
}
=> Override the Index() method to fetch the sample record and pass it to the view
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
using (var session = MvcApplication.Store.OpenSession())
{
var rp = session.Load<RavenPerson>("/Person/1");
return View(rp);
}
}
=> Override index.cshtml
@model MvcApplicationForRavenDb.Controllers.RavenPerson
@{
ViewBag.Title = "Home Page";
}
<h2>@ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
</p>
<p>The Raven Person should be [@Model.Name]</p>
- F5
Welcome to ASP.NET MVC!
To learn more about ASP.NET MVC visit http://asp.net/mvc.
The Raven Person should be [Pat]
Thursday, May 24, 2012
Jeremy Likness built a lightweight, NoSQL , object-oriented database for .Net 4.0 called Sterling.
First, wow.
Second, links:
- CodePlex: http://sterling.codeplex.com/
- WikiPedia: NoSql http://en.wikipedia.org/wiki/NoSQL
Third, here’s a very brief example of console application that uses Sterling, it takes about 5-10 minutes in all.
Here are the steps
- Create an empty Console Application
- install NuGet (if not already done)
- install Sterling
- Create a New Foo Class (this will be my model)
- Create New HelloDatabaseInstance Class (this will be my database)
- Override the Name
- Override the RegisterTables
- Modify the Main
- F5
Here are the details (Pardon my source code formatting…)
Create an empty Console Application

I created an app called “ConsoleApplicationForTestingSterling”
Install NuGet (if not already done)
Install Sterling

Create a New Foo Class (this will be my model)
Here’s the class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplicationForTestingSterling
{
public class Foo
{
public int Id { get; set; } public string Name { get; set; } public bool IsBar { get; set; } public DateTime SomeDate { get; set; }
}
}
Create New HelloDatabaseInstance Class (this will be my database)
The HelloDatabaseInstance is what you create to help Sterling know about the objects that you’ll be storing in the database (since Sterling is an Object Oriented Database, of course).
- Must inherit from BaseDatabaseInstance
- Override the Name
- Override the RegisterTables
you basically create one “table” by base object that you’ll be storing in the database.
Person, Organization, Vehicle, Course, Order, etc. Note that each object you’re storing can have sub-items, like an Order has OrderDetails, but you simple store Order and Sterling will make sure to store the OrderDetails too.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Wintellect.Sterling.Database;
namespace ConsoleApplicationForTestingSterling
{
public class HelloDatabaseInstance : BaseDatabaseInstance
{
public override string Name
{ get
{ return "HelloDatabase"; }
}
protected override List<ITableDefinition> RegisterTables()
{ return new List<ITableDefinition>
{ CreateTableDefinition<Foo,int>(foo => foo.Id)
};
}
}
}
Modify the Main
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Wintellect.Sterling;
namespace ConsoleApplicationForTestingSterling
{ class Program
{static void Main(string[] args)
{ SterlingEngine v8 = new SterlingEngine();
v8.Activate();
var path = "Hello/"; // NOTE: the trailing slash "/" is important
var driver = new Wintellect.Sterling.Server.FileSystem.FileSystemDriver(basePath:path);
ISterlingDatabaseInstance db = v8.SterlingDatabase.RegisterDatabase<HelloDatabaseInstance>(driver);
Console.WriteLine("Ready..");
Foo f = new Foo();
f.Id = 25;
f.Name = "Twenty Five";
f.IsBar = true;
f.SomeDate = DateTime.Today;
Console.WriteLine("Saving Foo 25"); db.Save<Foo>(f);
Console.WriteLine("Fetching Foo 25");
Foo f25 = db.Load<Foo>(25);
Console.WriteLine("Foo 25.Name = [{0}]",f25.Name);
v8.Dispose();
Console.WriteLine("..Done"); Console.ReadLine();
}
}
}
F5
Ready..
Saving Foo 25
Fetching Foo 25
Foo 25.Name = [Twenty Five]
..Done
Friday, January 29, 2010
Coding standards is to programming what walking is to a fashion show.
You can have the best coding standards and rules to force everyone to obey them, but if what you're building is crap, the walk won't help much.
Pat
Wednesday, January 6, 2010
When creating a new SCSF project, the "solution recipe" does a great job creating the basis, but there's a few things you need to know/adjust. Since creating SCSF solutions/projects isn't an activity we do on a day-to-day basis, we tend to forget these quirks.
The name you give your "solution" will be used in the assembly name (the output dll) and the namespace of the projects. So the Infrastructure.Interface will have an assembly name "MySolution.Infrastructure.Interface.dll", and the namespace will be MySolution.Infrastructure.Interface. The same will be for the MySolution.Infrastructure.Library, MySolution.Infrastructure.Module, MySolution.Infrastructure.Shell and MySolution.Infrastructure.Layout.
The problem is that the ProfileCatalog.xml is generated with assembly names without the "MySolution" prefix. So, starting the solution will return a "ModuleLoadException was unhandled" error.
Don't change the values from the ProfileCatalog.xml. Instead, change the AssemblyName to a dll name without the "MySolution" prefix, because...
When you later create a "business/fundation" project, the recipes like "Add View (with Presenter)" are expecting projects that reference the "Infrastructure.Interface.Dll" If there's a prefix like "MySolution.Infrastructure.Interface.dll" the recipe/package won't be enabled.
Thursday, May 28, 2009
So...
You're trying to debug an application that is using a large (100> tables) database and for a reason or an other, you don't understand where this darn Id is coming from!
Here's a simple Sql Script that browses your database for this darn Id...
Just change the @Id = '' for the id you're looking for!
declare @id as varchar(50)
-- idToRemove.ToString() "" String
select @id = '2eda82d8-9dcb-dd11-965a-001279d8c645'
set nocount on
declare @template as varchar(5000)
set @template = 'declare @id as uniqueidentifier
select @id = ''' + @id + '''
if exists(select * from [<%=TableName%>] where <%=ColumnNamesToInclude%>)
begin
print ''<%=TableName%>''
select * from [<%=TableName%>] where <%=ColumnNamesToInclude%>
end'
declare @TableName as varchar(250)
DECLARE TAB_CURSOR CURSOR FOR
SELECT TABLE_NAME as TableName
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
ORDER BY TableName
OPEN TAB_CURSOR
FETCH NEXT FROM TAB_CURSOR
INTO @TableName
WHILE @@FETCH_STATUS=0
BEGIN
DECLARE @sql as varchar(max)
DECLARE @columnCount as int
DECLARE @colName as varchar(500)
DECLARE @columnDataType as varchar(50)
DECLARE @delimiter as varchar(10)
DECLARE @columnNamesToInclude as varchar(max)
-- print '--------------------------------------------------------'
-- print '-- '
-- print '-- ' + @tableName
-- print '-- '
-- print '-- '
set @colName = ''
set @delimiter = ''
set @columnNamesToInclude = ''
set @columnCount = 0
DECLARE COL_CURSOR CURSOR FOR
select COLUMN_NAME as [columnName]
FROM INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = @tableName
AND DATA_TYPE = 'uniqueidentifier'
ORDER BY ORDINAL_POSITION
OPEN COL_CURSOR
FETCH NEXT FROM COL_CURSOR
INTO @colName
WHILE @@FETCH_STATUS=0
BEGIN
set @columnCount = @columnCount + 1
set @columnNamesToInclude = @columnNamesToInclude + @delimiter + '[' + @colName + '] = @Id'
set @delimiter = ' OR '
FETCH NEXT FROM COL_CURSOR
INTO @colName
END
CLOSE COL_CURSOR
DEALLOCATE COL_CURSOR
if @columnCount > 0
BEGIN
set @sql = @template
set @sql = replace(@sql, '<%=TableName%>', @tableName)
set @sql = replace(@sql, '<%=ColumnNamesToInclude%>', @columnNamesToInclude)
set @sql = replace(@sql, '<%=ScriptDateTime%>', getdate())
exec (@sql)
-- print ''
-- print ''
-- print @sql
-- print ''
-- print 'GO'
-- print ''
-- print ''
-- print ''
-- print ''
END
FETCH NEXT FROM TAB_CURSOR
INTO @tableName
END
CLOSE TAB_CURSOR
DEALLOCATE TAB_CURSOR
Wednesday, May 6, 2009
A long time ago, I wrote a very simple log method. It was used to print the number of times a function/method was called. I just changed it a bit and added an optional dumpAll parameter for printing the whole results.
It tracks 3 things: the name of the method, the number of times it was called, and stack level.
Private Sub log(Optional ByVal dumpAll As Boolean = False)
Static Dim h As New Generic.Dictionary(Of String, Integer)()
If dumpAll Then
For Each d As Generic.KeyValuePair(Of String, Integer) In h
System.Diagnostics.Debug.Print("[{0}] was called [{1}] times", d.Key, d.Value)
Next
Else
Dim st As New StackTrace(True)
Dim m As System.Reflection.MethodBase = st.GetFrame(1).GetMethod()
If Not h.ContainsKey(m.Name) Then
h.Add(m.Name, 1)
Else
h(m.Name) = h(m.Name) + 1
End If
System.Diagnostics.Debug.Print("Method [{0}],(stack [{1}]),called [{2}] times", m.Name, st.FrameCount, h(m.Name))
End If
End Sub
All you need to do is to add the call to the log() method in your method/function.
Ex:
Public Overrides Sub MyMethod()
log()
mSomeThing.DoSomethingEl()
End Sub
It would be nice to have a macro/addin to add/remove this log() call to every method/function in a class. Mmm...
Pat
Wednesday, April 1, 2009
The Current state of Unit Testing in the software industry.
10 years ago, when the web boom started and applications were built with classic asp and ado, Unit Testing wasn't much on developers minds.
But, in 2009, I'm amazed by the lack of unit testing in most environments I've encountered...
In fact, most environments (not all, but most) I've seen don't even have a dedicated QA team, or even testers!!!
There are no excuses.
- Visual Studio (since 2005) have right-click generation of unit test projects and test method stubs.
- There are plenty of documentation, add ons/tools and frameworks.
- Most Pattern & Practices (CAB/SCSF, Prism, Asp.Net Mvc, etc.) are greatly oriented towards unit testing.
- We know now that the teams that include unit testing (especially TDD) are far more productive than teams that don't. (less time and better quality)
So, I blame:
- Superiors/Bosses without a vision
- Senior architects/developers without a vision or balls to push unit testing
- Architects that don't show the way, or even block it (yeah, I know)
- Project Leaders who didn't plan for Unit Testing in the "project plan"
- Developers/programmers who don't care
- Clients who accept mediocre applications
- "The Media" who doesn't care because Unit Testing is not sexy and doesn't bring in the dough
- A mentality of more "know-at-whatever-quality" instead of more "quality"
- Demos that become applications
- Sample Codes and snippets available on the web that hints away from Unit Testing
- Projects that are either already started without unit testing or that start with a demo ware attitude
- Microsoft for creating development tools that make it easier to write bad than it is to write good.
Visual Studio should have two template groups:
- Simple throw away templates (or templates that we have right now)
- Enterprise class templates
In 2) Enterprise templates, it should be impossible to write code that doesn't follow best practices like high availability, security, unit testability (if that word exists), for instance:
- Tight Coupling should be "deprecated" !
- The use of Interfaces should be mandatory ?
- Ho and so much more
Since Visual Studio can throw a warning if a variable is used before it's set, if a variable is just not used, or if not all paths within a function return a value, lets include "hey, this is bad code".
In the next few years, we'll see development environments, frameworks and utilities that will make it easier to do right than it is to do wrong, making this blog obsolete, in the meantime, push the Unit Testing.
Finally,
I blame myself not working hard enough in promoting/evangelizing unit testing with my colleagues..
Mea Culpa
Pat
Friday, March 13, 2009
Step 1: prepare yourself :)
The first step to show a report on a windows application is to figure out the url and path of the report!
Sql Reporting Services has 2 virtual directories:
- /Reports: renders the reports to the user in a user-friendly web page
- /ReportServer: executes the report
When your boss asks you to display a report in a winform application, he(she) will likely send you a url in the following forms:
The urls above don't translate to properties that you need in a winform (or webform)...
To "consume" a report from a winform, you can't use the url above, you need another url. Actually, you need two things:
- The Report Server Url
- The Path of the report
In the following two(2) steps, we'll see how to figure out how to discover that information.
Step 2: The Report Server Url
Ask your administrator, or find it for yourself! The Reporting Server url is hidden in the report!
- Open the report in a browser (ie)
- Right click on the report's header (ex: on the report's title) -> View Source
- Locate the meta tag "Report Server", the "Content" attribute will contain the Report Server value: ex
- <META Name="Report Server" CONTENT="HTTP://ReportingServices/reportserver">
{ReportServerUrl} = "HTTP://ReportingServices/reportserver"
Step 3: The Report Path
The Report Path is something you have to "build" manually.
- Open the ServerUrl in the browser (ie), a page with a list of folders will be shown
- Copy the text of the folder into notepad or something like that. (referred bellow as {MyFolder})
- Navigate to the folder, a page with a list of reports will be shown
- Copy the text of the report into notepad: (referred bellow as {MyReportName})
The ReportPath will be the following: /{MyFolder}/{MyReportName}
ex: {ReportPath} = "/TMF Reports/TMF1200 - Project Status"
Step 4: The ReportViewer in the Winform
Now, you're ready to add the report on the winform.
- Open the winform in Visual Studio
- Add a ReportViewer on the designer (Toolbox > Data > ReportViewer)
- Set the following properties:
Run the application.
Pat
Note To Microsoft
It would nice if it was easier to discover reports from within Visual Studio. A wizard where a dev could enter any of the following:
Thanks, Pat
Thursday, February 12, 2009
Well well well...
"Musician sells CD for $15.98, CD + live concert for $5000" http://www.boingboing.net/2009/02/11/musician-sells-cd-fo.html
From John Wesley Harding's web site:
"Who was Changed and Who was Dead"
is John Wesley Harding's first rock record since 2004.
It will be available in stores March '09.
But you can buy the album, not to mention download and hear it, immediately. There are various offers available to you.
I guess I wasn't too far off, was I ? http://geekswithblogs.net/Patware/archive/2007/05/31/112885.aspx
Put a base price for a base article, and premium pricing for premium articles!
I still believe that John should have offered an option "download the album for x$ and get a certificate of ownership" proving that the customer did pay for the mp3.
I don't bring my thousand CDs at work or on the bus. I buy my CDs second hand, rip them to MP3, put them on an external hard drive, and enjoy what I paid for at work or at home.
My CDs are stored in a fireproof enclosure.
Pat
Wednesday, November 12, 2008
The out-of-the-box StaticSiteMapProvider is great for, well, static web sites. I don't find the StaticSiteMapProvider (and web.sitemap) model very practical for the dynamic nature of web sites/applications and especially Asp.Net Mvc applications.
In an mvc application it's difficult to render a static sitemap that allows breadcrumbs like:
- Home
- Home > Cars
- Home > Cars > Porsche 911
- Home > Cars > Porsche 911 > Edit
For the sake of discussion, and to keep the discussion as small as possible
- Home: url = /default.aspx?
- Cars: url = /Cars/Index (Controller=Cars, Action=Index)
- Porsche 911: /Cars/View(id) (Controller=Cars, Action=View, id = id)
- Edit: /Cars/Edit(id) (Controller=Cars, Action=Edit, id = id)
I'd like to have breadcrumb generating proper title (localized please) and url. Maarten Balliauw wrote a nice MvcSitemapProvider where you can write a sitemap with dynamic. What I don't like with the approach by Mr Balliauw is that I have to create a separate file that needs to keep be synched with the application, ie if the controller changes, I need to remember to change the sitemap.
So I'm offering you my "version" of a SiteMapProvider. The angle I'm taking is to decorate classes and methods with an attribute and have a SiteMapProvider that uses builds the sitemap dynamically, using these attributes (with reflection).
I understand that reflection is slower than reading a static file, but from what I've found, the SiteMapProvider gets initialized once, on startup. Ho, and I'm no expert by the way.
First, I created a blank, new AspNet Mvc (beta) application. Then, I created 3 files:
- AspNetMvcSiteMapNode.cs
- AspNetMvcSiteMapProvider.cs
- AspNetMvcSiteNodeAttribute.cs
We'll see them in details bellow, but first, let me show you how the "decoration" looks. In the HomeController.cs, I decorated the "out-of-the-box" Index and About actions, and created another action called View, Here a sample using the About and Item actions.
[AspNetMvcSiteNode(Key = "HomeIndexAbout", Title = "About", Description = "Description of us", ParentKey = "HomeIndex", Url = "/Home/About")]
public ActionResult About()
{
ViewData["Title"] = "About Page";
return View();
}
[AspNetMvcSiteNode(Key = "HomeItem", Description = "An item, simple one", IsDynamic = true, ParentKey = "HomeIndex", Title = "Item {id}", Url = @"/Home/Item/\b(?<id>\d+)")]
public ActionResult Item(int id)
{
SiteMap.CurrentNode.Title = string.Format("Item - foo[{0}]", id);
ViewData["id"] = id;
return View();
}
My first "pass" at the attribute pattern above was to rely on the Provider to magically render the Title at run-time based on the "rawUrl" parameter, and a mix of title and DynamicUrl regex pattern. It didn't turn out that well, more details at the end of the post.
So, instead of relying in the Provider, I decided to simply overwrite the node's Title myself in the actual "action".
SiteMap.CurrentNode.Title = string.Format("Item - foo[{0}]", id);
With the "StaticSiteMapProvider", everything is, well, static... so the above doesn't work (pitty). But with the AspNetMvcSiteMapNode provider, I made sure that SiteMapNodes are NOT readonly ;).
In the "Edit" action, I'm actually updating the "parentNode's" title !
[AspNetMvcSiteNode(Key = "HomeItemEdit", Description = "Edit of the item, simple one", IsDynamic = true, ParentKey = "HomeItem", Title = "Edit", Url = @"/Home/Edit/\d+")]
public ActionResult Edit(int id)
{
SiteMap.CurrentNode.ParentNode.Title = string.Format("Item - foo[{0}]", id);
SiteMap.CurrentNode.ParentNode.Url = "/Home/Item/" + id;
ViewData["id"] = id;
ViewData["name"] = id.ToString();
return View();
}
The AspNetMvcSiteNodeAttribute.cs class is very basic:
public class AspNetMvcSiteNodeAttribute : Attribute
{
public string Key { get; set; }
public string Url { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ParentKey { get; set; }
public bool IsDynamic { get; set; }
public bool IsRoot { get; set; }
}
Nothing fancy. The Key could actually be generated automatically, via a Guid, but it would be difficult to build the parent/child relationship with randomn data.
I also created a AspNetMvcSiteMapNode.cs class, that inherits from the SiteMapNode and implements the "dynamic" portion.
public class AspNetMvcSiteMapNode : SiteMapNode
{
/// <summary>
/// If the url is dynamic (variable on the querystring, for example), set the value to True
/// </summary>
public bool IsDynamic { get; set; }
public string DynamicUrl { get; set; }
public string ParentKey { get; set; }
public AspNetMvcSiteMapNode(SiteMapProvider provider, string key)
: base(provider, key)
{
IsDynamic = false;
}
}
The Provider AspNetMvcSiteMapProvider.cs class, that inherits from the SiteMapProvider uses Reflection to get the AspNetMvcSiteNodeAttribute. The algorithm includes a synchronization with the roles (via the AuthorizeAttribute).
This is far from production ready code!!!!
public class AspNetMvcSiteMapProvider : SiteMapProvider
{
private Dictionary<string, AspNetMvcSiteMapNode> _nodes;
private AspNetMvcSiteMapNode _rootNode;
public override SiteMapNode FindSiteMapNode(string rawUrl)
{
foreach (KeyValuePair<string, AspNetMvcSiteMapNode> kvp in _nodes)
{
if (kvp.Value.IsDynamic)
{
Regex regex = new Regex(kvp.Value.DynamicUrl);
if (regex.IsMatch(rawUrl))
{
kvp.Value.Url = rawUrl;
int[] groupNumbers = regex.GetGroupNumbers();
Match match = regex.Matches(rawUrl)[0];
for (int i = 1; i < groupNumbers.Length; i++)
{
Group group = match.Groups[i];
kvp.Value.Title = kvp.Value.Title.Replace("{" + regex.GroupNameFromNumber(i) + "}", group.Value);
}
return kvp.Value;
}
}
else
{
if (kvp.Value.Url.ToUpper() == rawUrl.ToUpper())
{
return kvp.Value;
}
}
}
return null;
}
public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
{
SiteMapNodeCollection coll = new SiteMapNodeCollection();
foreach (KeyValuePair<string, AspNetMvcSiteMapNode> kvp in _nodes)
{
if (kvp.Value.ParentKey != null && kvp.Value.ParentKey == node.Key)
{
coll.Add(kvp.Value);
}
}
return coll;
}
public override SiteMapNode GetParentNode(SiteMapNode node)
{
if (node != null && node.Key != null && node.Key != string.Empty && _nodes.ContainsKey(node.Key))
{
AspNetMvcSiteMapNode aNode = _nodes[node.Key];
if (aNode.ParentKey != null && aNode.ParentKey != null && _nodes.ContainsKey(aNode.ParentKey))
{
return _nodes[aNode.ParentKey];
}
else
return null;
}
else
return null;
}
protected override SiteMapNode GetRootNodeCore()
{
return _rootNode;
}
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection attributes)
{
base.Initialize(name, attributes);
_nodes = new Dictionary<string, AspNetMvcSiteMapNode>();
Assembly a = Assembly.GetExecutingAssembly();
foreach (Type t in a.GetTypes())
{
Attribute[] allAttributes = (Attribute[])t.GetCustomAttributes(typeof(AspNetMvcSiteNodeAttribute), true);
foreach (Attribute att in allAttributes)
{
if (att.GetType() == typeof(AspNetMvcSiteNodeAttribute))
{
addMvcNodeFromAttribute((AspNetMvcSiteNodeAttribute)att, null);
}
}
foreach (MethodInfo mi in t.GetMethods())
{
foreach (Attribute att in mi.GetCustomAttributes(true))
{
if (att.GetType() == typeof(AspNetMvcSiteNodeAttribute))
{
addMvcNodeFromAttribute((AspNetMvcSiteNodeAttribute)att, mi);
}
}
}
}
}
private void addMvcNodeFromAttribute(AspNetMvcSiteNodeAttribute aspNetMvcSiteNodeAttribute, MethodInfo methodInfo)
{
AspNetMvcSiteMapNode node = new AspNetMvcSiteMapNode(this, aspNetMvcSiteNodeAttribute.Key);
node.Title = aspNetMvcSiteNodeAttribute.Title;
node.Description = aspNetMvcSiteNodeAttribute.Description;
if (aspNetMvcSiteNodeAttribute.IsRoot)
_rootNode = node;
else
{
node.ParentKey = aspNetMvcSiteNodeAttribute.ParentKey;
}
node.ReadOnly = false;
node.IsDynamic = aspNetMvcSiteNodeAttribute.IsDynamic;
if (node.IsDynamic)
{
node.DynamicUrl = aspNetMvcSiteNodeAttribute.Url;
}
else
{
node.Url = aspNetMvcSiteNodeAttribute.Url;
}
if (methodInfo != null)
{
setNodeFromMethodInfo(methodInfo, node);
}
_nodes.Add(node.Key, node);
}
private static void setNodeFromMethodInfo(MethodInfo methodInfo, AspNetMvcSiteMapNode node)
{
foreach (Attribute authAtt in methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true))
{
if (authAtt.GetType() == typeof(AuthorizeAttribute))
{
AuthorizeAttribute authorizeAttribute = (AuthorizeAttribute)authAtt;
string[] roles = authorizeAttribute.Roles.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
foreach (string role in roles)
{
node.Roles.Add(role);
}
}
}
}
}
Note that the AspNetMvcSiteNodeAttribute can be applied to any class. For example, on the "Default.aspx.cs" class, I decorated the page_load method like this:
public partial class _Default : Page
{
[AspNetMvcSiteNode(IsRoot=true,Key="Root", Url="/Default.aspx?", Title="Home", Description="The site's home page")]
public void Page_Load(object sender, System.EventArgs e)
{
HttpContext.Current.RewritePath(Request.ApplicationPath);
IHttpHandler httpHandler = new MvcHttpHandler();
httpHandler.ProcessRequest(HttpContext.Current);
}
}
In the code above (and in the attribute), I have to specify the url. I don't like that. I really would like to forget about that "static" url and rely on the System.Web.Mvc to generate the proper urls in the case of controller/action methods. But my attempts to make it work failed...
If the first page to load the web site in IIS is "/Default.aspx", then the HttpContext .Current.Handler is not the MvcHandler. So I can't leverage the Routing. If the first page loaded is handled by the MvcHandler, everything is fine. Since the Provider's "initialize" gets fired once, at startup, I can't rely on the fact that it will always be the MvcHandler.
The HomeController.cs code is like this:
namespace MvcApplication1.Controllers
{
[HandleError]
[AspNetMvcSiteNode(Key="HomeController", Title="Home", Description="Home Page", Url="/Home", ParentKey="Root")]
public class HomeController : Controller
{
[AspNetMvcSiteNode(Key="HomeIndex", Title="Index", Description="Description of Index", Url="/Home/Index", ParentKey="Root")]
public ActionResult Index()
{
for (int i = 0; i < 10; i++)
{
AspNetMvcSiteMapNode node = new AspNetMvcSiteMapNode(SiteMap.Provider, "HomeItem_" + i.ToString());
node.Url = "/Home/Item/" + i.ToString();
node.Title = string.Format("Item [id={0}]", i);
node.IsDynamic = false;
SiteMap.CurrentNode.ChildNodes.Add(node);
}
ViewData["Title"] = "Home Page";
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
[AspNetMvcSiteNode(Key = "HomeIndexAbout", Title = "About", Description = "Description of us", ParentKey = "HomeIndex", Url = "/Home/About")]
public ActionResult About()
{
ViewData["Title"] = "About Page";
return View();
}
[AspNetMvcSiteNode(Key = "HomeItem", Description = "An item, simple one", IsDynamic = true, ParentKey = "HomeIndex", Title = "Item {id}", Url = @"/Home/Item/\b(?<id>\d+)")]
public ActionResult Item(int id)
{
SiteMap.CurrentNode.Title = string.Format("Item - foo[{0}]", id);
ViewData["id"] = id;
return View();
}
[AspNetMvcSiteNode(Key = "HomeItemEdit", Description = "Edit of the item, simple one", IsDynamic = true, ParentKey = "HomeItem", Title = "Edit", Url = @"/Home/Edit/\d+")]
public ActionResult Edit(int id)
{
SiteMap.CurrentNode.ParentNode.Title = string.Format("Item - foo[{0}]", id);
SiteMap.CurrentNode.ParentNode.Url = "/Home/Item/" + id;
ViewData["id"] = id;
ViewData["name"] = id.ToString();
return View();
}
}
}
You have my code, so go ahead and play with it. If you find improvements, let me/us know.
Regex in the DynamicUrl
As mentionned above, my first "pass" at the attribute pattern above was to rely on the Provider to magically render the Title at run-time based on the "rawUrl" parameter, and a mix of title and DynamicUrl regex pattern. But this idea only works if the value you want to show in the Title is the "id" !
- Home > Cars [25] // ok because id=25 is the value to show.
- Home > Cars [Porsche] // impossible because the provider can't render "Porsche" from the id 25... so, problem 1
Problem 2, the "rawUrl" sent to the method FindSiteMapNode(string rawUrl) only works for the "current node", so the: Home > Cars [25] > Edit wouldn't be possible, because the "Cars [25]" portion would actually be rendered by the "parent" url being the "view", not the "edit".
So I kept the regex algorithm just in case it would be useful for someone someday. Check the: public override SiteMapNode FindSiteMapNode(string rawUrl) Method from the Provider to see how I'm using it.
Have fun...... life's short.
Pat
I finally and succesfully migrated a VSS 2005 Database to TFS 2008.
I got soo many problems/errors. Things like:
- Migration tool worked, but only the folders have been created in TFS. No file has been created,
- Another migration warned that TF60085: No file or folder to migrate
- DCOM errors on the server.
- When re-creating a TFS Project, TF30162: Task "WITs" from Group "WorkItemTracking" failed
So, after migrating "empty folders" the first time, I tried to delete the projects in TFS and re-importing them... It turned to be a mess......... I had to run the TFSDeleteProject.exe...
Here are my findings that may or may not be evident for mortals.
- You must run the VSSConverter.exe from a "client" computer (not the TFS server)
- The account you're using must have "project creation" priviliges on the TFS server
- Check for DCOM errors on the server
If I had time and gutts, I'd start all over again (new os and all) and re-run everything with the following steps just to QA my steps!
To fix the DCOM errors,
Open Component Services
-
Start-->Administrative Tools-->Component Services
-
Expand Component Services, Computers, My Computer, DCOM Config.
-
Find the application (IIS WAMREG Admin Service / CLSID {61738644-F196-11D0-9953-00C04FD919C1}).
-
Right-Click-->Properties and select the Security tab.
-
For the "Launch and Activation Permissions, ensure that the Customize radio button is selected, and click Edit.
-
Add your service account (check the DCOM error message in the event viewer to find the right one), in my case, it was "NT AUTHORITY\NETWORK SERVICE"
-
Restart IIS and continue on.
This "should" fix the DCOM errors in the future.. hehe..
As for the TF30162: Task "WITs" from Group "WorkItemTracking" failed. I don't understand, but after "browsing" IIS console, re-starting IIS, I was able to re-create a new TFS Project without the TF30162 error above. mmm. maybe we just need to wait a few minutes (for sub-processes to finish/garbage collect) and a restart iis to clear caching. anywhoooo
ok, now back to migration
Perform the following steps from the client computer unless noted.
Step 1. Test Project creation on TFS
Open Visual Studio, connect to the TFS Server, try to create a project and upload a file to the Source Control. If all works, continue, if not, fix !
Step 2: Analyze the VSS
Run the Analyze.exe. Something like
"C:\Program Files\Microsoft Visual Studio\VSS\win32\ANALYZE.EXE" -f -c -d -v1 "d:\vss\data"
Step 3: get rid of checked outs files
There will likely be files that are checked out (older/defunct projects or un-monitored projects).
- Open VSS (client)
- Search/Status Search
- Choose "Display all checked out files"
- Search Area "Search in in all projects"
If there are files found, send an email to your team or kill/"Undo Checkout" all files.
- Open VSS client as admin
- Right click the root "$/"
- Choose Undo Checkout
- Recursive = True, Local Copy = Leave, ok
- Confirm all mesages...
Step 4: install proper sps and hot fixes
if you run the VSSConverter and run into something like this:
Initializing...
VSSConverter has detected that Visual SourceSafe does not have the recommended u
pdates installed. To ensure optimal results, install the updates referred to in
Knowledge Base Article 950185. Proceeding without these updates may lead to pr
oblems during migration. Continue the migration without the updates (Y/N)?n
That's because the VSSConverter.exe needs to be updated (hotfix).
Well, kb950185, although the information seems to be correct, it wasn't clear that the hotfix can be found at http://code.msdn.microsoft.com/ (the url is misleading, I think).
Also, on that code.msdn page, to download the actual "exe", you have to click on "Current release"... Here's the direct link to the english (international) download page: http://code.msdn.microsoft.com/KB950185/Release/ProjectReleases.aspx?ReleaseId=1123
At the time of this writting, I have the following (all in English language, OSes and software):
Client:
- Visual SourceSafe 2005 + Visual SourceSafe 2005 SP1
- Visual Studio 2008 + Visual Studio 2008 SP1
- TFS 2008 Explorer
Server:
- Team Foundation Server 2008 + TFS 2008 Explorer + TFS SP1
Note that all three (VSS client, TeamFoundationClient/Explorer and VSSConverter) must have the same language in order for VSSConverter to work.
Step 5: Run!
Follow the "standard" steps to migrate. Here's my settings.xml file:
<?xml version="1.0" encoding="utf-8"?>
<SourceControlConverter>
<ConverterSpecificSetting>
<Source name="VSS">
<VSSDatabase name="E:\VSS\Patware1.0" />
<UserMap name="E:\tfs\usermapPatware1.0.xml" />
</Source>
<ProjectMap>
<Project Source="$/" Destination="$/Patware/"/>
</ProjectMap>
</ConverterSpecificSetting>
<Settings>
<!--<Output file="E:\tfs\analysisPatware1.0.xml" />-->
<TeamFoundationServer name="turner" port="8080" protocol="http"></TeamFoundationServer>
</Settings>
</SourceControlConverter>
I "basically" used the same file for the analyze and the migrate portion. I simply commented out the proper settings portion.
Glad it can help if it does!
Pat
Thursday, October 30, 2008
Hi,
I just "migrated" from ASP.Net MVC Preview 5 to ASP.Net MVC Beta. I uninstalled Preview 5 and installed the Beta. I already had checked out previous previews but spent the most time with Preview 5, and didn't want to upgrade my "Preview-5-version-of-my-test-website" just in case there were deprecated or new features, specially in the "New ASP.Net MVC Application" template. Gutt feeling. So I created a new Project.
In Preview 5 release, the Html.ActionLink accepted a generic parameter list, with lambda expression, allowing to strongly-type the controller and it's method, including its parameters. I really like this right now (my bubble may blow one day).
The syntax would look like this:
<%= Html.ActionLink<CalveNet.Controllers.PatwareController>(c => c.Index(), "Here")%>
After creating my project, I copied a few files over (aspx pages, controllers, etc.). But Visual Studio complained that the
"The non-generic method 'System.Web.Mvc.Html.LinkExtensions.ActionLink(System.Web.Mvc.HtmlHelper, string, string)' cannot be used with type arguments"
I found out that this generic/lambda version of the parameter is from a separate dll, called "ASP.NET MVC Beta Futures".
To make it work, unzip the file, include the "Microsoft.Web.Mvc.dll" to your references, and don't forget to include the namespace in your web.config, under the path:
/configuration/system.web/pages/namespaces
add the following: <add namespace="Microsoft.Web.Mvc"/>
Pat
Wednesday, October 15, 2008
Hi there,
How do you mock an asynchronous web method (web service) call? You can skip the background and move on directly to the solution...
Background:
In a CAB/SCSF project I'm working on, I'm testing a Presenter's method "OnViewReady". In my implementation, this OnViewReady does basically 2 things:
- Tells the View to Show a "Loading..." message to the user
- Issues an asynchronous call to a Web Service
When the call is returned from the Web Service, the Presenter tells the View to Show the message returned by the Web Service.
The method looks like this:
Solution
The solution is threefold:
- Make the Web Service Mocking Friendly ...
- Make the presenter "interface friendly" ...
- Fine tune the Test Method! :) ...
Web Service Mocking friendly
Making the Web Service Mocking friendly was not so evident. Well, at least, the "mocking async methods" part.
To mock a Web Service, you have to extract an interface from the generated web service code (reference.cs), and make the web service code inherit from that interface. You could make the web service "reference.cs" implement the interface, but may kill that code when you "update web reference". The trick is to add a second "partial class" that will inherit from that interface.
Here goes:
- In Visual Studio,
- In your MyAppModule's project, click Add Web Reference and follow the wizard
- Open the Reference.cs in the IDE (Show All Files)
- Right Click on the public partial class MyWebService and choose "Refactor -> Extract interface"
- This will create a IMyWebService interface with all of the methods from you Web Service.
- Now, add a new class "MyWebService", make sure the namespace and class declaration is the same as the "real" web service. "public partial class MyWebService", but make it implement the interface you created.
This is the web service's signature (in my example, the "MyWebService" is called "GeneralWS")
public partial class GeneralWS : System.Web.Services.Protocols.SoapHttpClientProtocol { .. }So nothing changed.
This is the interface extracted:
public interface IGeneralWS
{
void CancelAsync(object userState);
string Ping();
void PingAsync(object userState);
void PingAsync();
event PingCompletedEventHandler PingCompleted;
string Url { get; set; }
bool UseDefaultCredentials { get; set; }
}
This is the second partial class. Note that there's no code, just the interface:
public partial class GeneralWS : IGeneralWS
{
//look ma, no hands
}
Now, if you open the reference.cs, check the PingCompletedEventArgs code: The constructor is "internral" ! So, this means that we can't mock the async method because we can't create a new PingCompletedEventArgs when we mimic the callback.
Again, what you do, is create an interface and leverage the "partial" declaration of the PingCompletedEventArgs. Repeat the steps for the Web Service class, but this time, for the EventArgs.
Here are my results:
public interface IPingCompletedEventArgs
{
string Result { get; }
}
Add the second "partial" class:
public partial class PingCompletedEventArgs : IPingCompletedEventArgs
{
public PingCompletedEventArgs(string results) :
base(null, false, null)
{
//We're hard coding the results to an 1-item array (object) since we know
//that the web service's Ping method returns a string.
this.results = new object[1];
this.results[0] = results;
}
}
Ok, that takes care of the "Web Service" part. At this point, you can run the solution and it will work. And if you update the web reference, you won't loose a thing.
Making the presenter "interface friendly"
Making the presenter "interface friendly" is the well-known "multiple constructor" algorithm.
In a classic web service call, your method will be like this:
private void doSomethingWild()
{
GeneralWS gws = new GeneralWS();
string returnValue = gws.Ping();
View.ShowMessage(returnValue);
}
In a "mocking friendly" class, you can't do that. The class must work with an interface instead and the mocking framework will "mock" that interface for you.
4 steps:
- Add a private variable of type Interface (that you created earlier)
- Add a default constructor that will attach a "real web service" to that private interface variable
- Add a "mockig friendly" constructor where pass in in the mocked interface (this is the real trick)
- Change the call to the web service to not create a reference to the "real web service", but simply re-use the private variable of type interface
here's a sample from my presenter (construction part):
public partial class HelloWorldViewPresenter : Presenter<IHelloWorldView>
{
private PeopleCentralGeneral.IGeneralWS _gws;
//default parameter-less Presenter, for normal code execution
public HelloWorldViewPresenter()
{
_gws = new GeneralWS();
_gws.PingCompleted += new PingCompletedEventHandler(gws_PingCompleted);
}
//special constructor for "Mocking Friendlyness"
public HelloWorldViewPresenter(IGeneralWS gws)
{
_gws = gws;
_gws.PingCompleted += new PingCompletedEventHandler(gws_PingCompleted);
}
...
/// <summary>
/// This method is a placeholder that will be called by the view when it has been loaded.
/// </summary>
public override void OnViewReady()
{
View.ShowMessage("Loading...");
base.OnViewReady();
pingAsync();
}
private void pingAsync()
{
//note that I'm not creating a new reference to the web service but re-using the interace instead..
//proper coding would check that _gws isn't null, of course ;)
_gws.PingAsync();
}
private void gws_PingCompleted(object sender, PingCompletedEventArgs e)
{
View.ShowMessage(e.Result);
}
...
}
Fine tuning the Test Method
Fine tuning the Test Method for async web methods requires another trick with the Web Service.
Finally, the Test Method !
In the HelloWorldViewPresenterTest.cs, here's the Test Method for the OnViewReady:
/// <summary>
///A test for OnViewReady ()
///</summary>
[TestMethod()]
public void OnViewReadyTest()
{
MockRepository repo = new MockRepository();
//the mocked general web service
IGeneralWS genWS = repo.StrictMock<IGeneralWS>();
//the mocked view
IHelloWorldView view = repo.StrictMock<IHelloWorldView>();
Support.TestableRootWorkItem workitem = new Support.TestableRootWorkItem();
//this will be used to simulate the call back from the web service
Rhino.Mocks.Interfaces.IEventRaiser pingCompletedRaiser;
using (repo.Record())
{
Expect
.Call(delegate { view.ShowMessage("Loading..."); });
//Expect that the PingAsynch() method will be called
Expect
.Call(delegate { genWS.PingAsync(); });
//Provide the entry point for the call back.
genWS.PingCompleted += null;
pingCompletedRaiser = LastCall
.IgnoreArguments()
.GetEventRaiser();
Expect
.Call(delegate { view.ShowMessage("Pong"); });
}
HelloWorldViewPresenter target = new HelloWorldViewPresenter(genWS);
target.View = view;
target.WorkItem = workitem;
using (repo.Playback())
{
target.OnViewReady();
//This "new EventArgs" would be impossible without the second partial class
PingCompletedEventArgs args = new PingCompletedEventArgs("Pong");
//make the callback call....
pingCompletedRaiser.Raise(genWS, args);
//we're done!
}
}
I hope this info will help you in your quest for building better applications.
Patrice Calvé
Tuesday, September 30, 2008
You know you're a Visual Studio User when... You're typing a document in Microsoft Word and you find yourself hitting CTRL-<space>+TAB to auto-complete a long word and/or you're not too sure of the spelling (in my case, it was hierarchical)!