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 06, 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 01, 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)!
Wednesday, May 28, 2008
So,
You have a Web Service and a test client. They (server + client) have been working great for months and all over sudden you're getting weird values. The values sent from the server seem good, but the Xml/SOAP received doesn't get de-serialized correctly, and everything (or some properties) is set to default values (int=0, guid={00000000-0000-0000-0000-000000000000}, ...). Read on ! 
The test client has a web reference to your Web Service. A few web methods are returning objects (simple objects like Country(Id, NameEn, NameFr, etc.)).
When you run the Web Service from IE, the result is as expected:
<?xml version="1.0" encoding="utf-8" ?>
- <ArrayOfCountry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://tempuri.org/People">
But for a reason or another, when you run it from your Test Client, every darn CountryId is set to 0. If the the Id is a Guid, every row has a MyId = {00000000-0000-0000-0000-000000000000}.
Try updating your Web Reference(s)! Right click the Web References -> Update Web Reference.
Restart your test client.
How many times have I fallen in this trap?
I hope this post will make me remember this mental note, and enlighten you!
Have fun, life's short
Pat
Thursday, May 01, 2008
This morning, I received an invitation from a friend to join http://www.reunion.com/.
I like to discover new "networking" sites and the way they each have their own flavor. So I joined in.
The registration is nice and works as expected for a "corporate application". After all, a site that uses SSL and "TRUSTe CERTIFIED PRIVACY" must be in the right path, right?
The first thing I should have done is check the "web" for known privacy concerns or blogs that talk about it, but I didn't and shame of me, mea culpa.
Once registered I did what I like to do when I join a new "networking" site, use the contact discovery tool that most have. As you may know, it's a functionality that downloads the contacts from another web site (gmail, hotmail, yahoo, linkedin, facebook, etc.) and offers you a few options.
The first thing most applications will do is tell you which downloaded contacts are already registered and offer you to "connect" with them.
The second thing most applications will do is offer you too choose the "un-registered" users in order to send them an invitation to join. The better applications will give you the ability to customize the message these people will see. It's particularly useful when the site isn't in the same language as the contacts you're inviting. right?
Well, from my experience with reunion.com, the contact discovery utility is a virus or Trojan. The site sent an email to everyone in my downloaded contact list without asking me first !
To everyone that received the reunions invitation on my behalf, I'm sorry..... it won't happen again, trust me !
Shame on you reunion.com.
1 minute after finding out this problem, I unregistered from the site, hopping that the site was not a spamming site disguised as ligit site. Let's just say that I changed the password on the "contact list's web site" just in case.
Not impressed.
Pat
Tuesday, March 11, 2008
So, You've downloaded a video (screencast for example) and you can't hear a thing and you're running Vista? You know that your "setup" is correct because you can play music. You know that the video contains audio because you can see the oscilloscope/spectrum analyzer jump. Yet, there's no sound.
If you look at the audio codec (file -> properties), it will probably say that the audio is something "mono"...
I bet you have a fancy 8 channel audio card with rear panel: front, rear, side, sub/center, line in, line out and front panel: mic, headphones.
I also bet you have either earphones or left/right speakers in the "front jacks" of the "rear panel".
Well.... depending on your audio card drivers, make sure to specify that you're setup is a "Stereo Audio Channel" and not a "Quadraphonic" or "5.1 Surround" !
Right click the Volume icon from the system tray (or Control Panel -> Classic View -> Sound):

In the playback tab, you'll see a list of playback devices. If there's only "speakers", chances are that you need to update the audio card device drivers (check your manufacturer).
If your default (green checkbox) is "Speakers", click configure and make sure that the "audio channel" is set to stereo:
A trick, to see what "playback device" is currently used by the audio/video program is running, press play on the audio/video program (winamp, windows media player, etc.), the selected playback device will have the sound level progress bar moving.
Of course, your setup (and/or problem) will be probably quite different from mine. My computer is using an onboard RealTek audio card and I had to
update my driver since I had a very old driver and couldn't do anything above with the old drivers.
It's "probably" not a codec issue, but a configuration issue.
Good luck... don't pest.
Friday, February 29, 2008
Both words "database" and "dataset" are written (on a qwerty keyboard) with the left hand !
that's it... nothing more
Wednesday, February 20, 2008
This weekend, I had this bright idea to install Virtual Server 2005 R2 on my Vista dev box at home to test a theory.
Well, it seems to break Visual Studio's WebServer. When trying to run or debug an ASP.Net application, I was hit by a "
WebDev.WebServer.exe has stopped working".
I tried many things, as you can imagine, but a few were:
- Reboot
- Un-installed Virtual Server 2005
- Un-installed/re-installed the Web Development Feature from the VS installation.
- Re-ran the various combinations of aspnet_regiis.exe
There was an entry in the Event Log's Application Logs:
Fault bucket 118476878, type 5
Event Name: CLR20r3
Response: None
Cab Id: 0
Problem signature:
P1: webdev.webserver.exe
P2: 9.0.0.0
P3: 4731664b
P4: System
P5: 2.0.0.0
P6: 471ebf0d
P7: 2c04
P8: 40
P9: System.Net.Sockets.Socket
P10:
System.Net.Sockets.Socket...
I tried running webdev.webserver.exe manually from the command line:
C:\Program Files\Common Files\microsoft shared\DevServer\9.0
C:\Windows\Microsoft.NET\Framework\v2.0.50727
both worked (I tried port 8080 and 8888) (see note at the end)
Anyway, what seems to have fixed the problem was the following:
Disable IPv6 from your network adapters
Control Panel -> Network Connections -> for each adapter/connection: disable the TCP/IPv6
notes:
- When running the webDev exe manually, I didn't (and should have) try to run it with the same port that VS was trying with.
- I didn't try disabling IPv6 one connection at a time. Maybe 1 did the trick, not sure.
Ho yes, one weird thing: the web based admin configuration page worked when the Web page launched by VS didn't
Anyway, I'm fixed for now and need to move on.
If this article helps you, woohoo...
If it doesn't, you'll find an answer in google ;)
Pat