by Oliver
7. September 2017 00:13
For quite a long time, our team chose not to mess with our working TeamCity configurations, which compile, build, test, and deploy our code several times a day. Two weeks ago, we finally upgraded our last and at the same time biggest project discoverize to work with Visual Studio 2017. This allowed us to take a fresh look at the *cough* new C# language features that we have been ignoring for the last few years. But using any of them also meant having to upgrade our continuous integration infrastructure to support them. Here's what we've done. Update all TeamCity configurations If you use the MSBuild runner, now choose the Microsoft Build Tools 2017 as the MSBuild version and set the MSBuild ToolsVersion to 15.0: This will lead to the error that no Build Agents can be found for the given configuration because a requirement is not met: MSBuildTools15.0_x64_Path cannot be found. Install new Build Tools Thanks to this Stackoverflow answer I quickly learned that I had to install the Build Tools for Visual Studio 2017. You can get the web installer from here. More information about the options in the tool can be found on this page. The first screen shows the possible workloads (as of August 2017) with Web development build tools selected… … and the second screen shows the individual components selected (I actually unchecked all optional .NET Framework targeting packs): Restart the TeamCity Agent Service For TeamCity to realize that you've installed new tools on you build machine, you need to restart the Agent Service. You can find it e.g. after running services.msc from the Start menu –> Run command. Missing AllRules.ruleset file Now, the compilation of our C# 6 project finally succeeded. There was still one problem: the build log contained warnings about a AllRules.ruleset file missing. I just went ahead and copied the file from my local machine (including the full folder hierarchy) because I could not find any information on where to find this file other than on my own machine (with Visual Studio installed). After that last step, the build log is finally black again. Happy configuring!
by Anton
7. November 2014 15:47
For our new time tracking web application (which not yet live) we need the functionality to create a PDF report. Since we use AngularJS to generate the HTML for the reports, I thought it would be nice to just convert that HTML via JavaScript into a PDF. The search for a solution pointed me to jsPDF – a pretty good tool for creating PDF via JavaScript. But its capabilites to convert HTML into PDF are limited (as of late 2014). We needed to convert a HTML table with all its styles into a PDF. I added jsPdfTablePlugin into the mix, but it still did not look good. So we abandonded the JavaScript way, and let the backend generate the PDF. I added an MVC view that displays the report, which is then captured via wkhtmltopdf. We already knew this process since we use it in our platform tool discoverize generating invoices. It works smoothly.
by Oliver
9. August 2014 12:52
Today, I found this dusty piece of code in our code base: Stone-age version public string GetIframeIds() { var result = new StringBuilder(); var first = true; foreach (var iframe in _iframes) { if (!first) result.Append(','); else first = false; result.Append("'" + iframe.ClientID + "'"); } return result.ToString(); } … and just had to rewrite it to this: Updated version public string GetIframeIds() { return string.Join(",", _iframes.Select(ifr => "'" + ifr.ClientID + "'")); } I couldn't bear but run some micro-performance test on these code snippets since StringBuilder is usually quite fast. I ran each of the snippets with an _iframes length of 30 in a loop of 10.000 iterations and yes, the first version is faster with 215ms vs. 360ms. But then, in production I run that code block only once per request, not 10.000 times as in the test. Spending 21ns or 36ns in that method won't make any significant difference, especially when looking at request execution times of beyond 100ms. Why should you or I care? The second code block is arguably easier to read, quicker to write, and harder to get wrong. Happy coding!
by Oliver
20. March 2013 15:24
While setting up a specification tests project for our new TeamReview tool, I was facing an HTTP 500.19 error when hosting our site in IIS Express. There are lots of questions on stackoverflow concerning this error, Microsoft has a whole page on it, but there is a whole bunch of suberrors that this error addresses. Error 0x8007007b: Cannot read configuration file Unfortunately, none of the above mentioned links contained or solved the specific error code I was seeing: Error Code 0x8007007b Config Error Cannot read configuration file Config File \\?\C:\Projects\_teamaton\teamreview\TeamReview.Specs\bin\Debug\..\..\..\TeamReview.Web\web.config After some reading, trying, fiddling, it appeared to me that maybe the path to the config file somehow messed up IIS Express. I admit that it was at least a bit unusual to use the parent directory dots. But it came from my test harness code where I wanted to use relative paths and used Path.Combine() to do that: var webPath = Path.Combine(Environment.CurrentDirectory, "..", "..", "..", "TeamReview.Web");
Pitfall: .. in path
Well, it turns out IIS Express didn't like it. Once I called it with a cleaned up path string, everything just worked:
"C:\Program Files (x86)\IIS Express\iisexpress.exe" /path:"C:\Projects\_teamaton\teamreview\TeamReview.Web" /port:12345 /systray:false
So, watch out for your physical path values when using IIS Express!
Use DirectoryInfo to navigate up your directory tree
To get the correct path without using absolute paths but also avoiding the .. I used the DirectoryInfo class:
var webPath = Path.Combine(
new DirectoryInfo(Environment.CurrentDirectory).Parent.Parent.Parent.FullName, "TeamReview.Web");
by Oliver
19. November 2012 21:09
I’ve just spent much more time than I’d want on figuring out why the following code wouldn’t give me a checked checkbox, even when I set the ViewModel.Value to true: public class ViewModel {
public string Name { get; set; }
public dynamic Value { get; set; }
}
@Html.CheckBox(Model.Name, (bool?)Model.Value)
The problem is with the signature of CheckBox, of course! Instead of calling the one where the second argument is of type bool, it calls the one where the second arg is of type object!
// the method I thought I was calling
public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, bool isChecked) {
return CheckBox(htmlHelper, name, isChecked, (object)null /* htmlAttributes */);
}
// the method I in fact was calling
public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, object htmlAttributes) {
return CheckBox(htmlHelper, name, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
// the method that's being called by the second method above: isChecked == null!
public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, IDictionary<string, object> htmlAttributes) {
return CheckBoxHelper(htmlHelper, null, name, null /* isChecked */, htmlAttributes);
}
Even though they were not strictly necessary, still, the framework sources helped me out this time to find this subtle bug (in my code, of course!).
The solution to the problem looks like this:
@Html.CheckBox(Model.Name, (bool)(Model.Value == true))
Happy Coding!
by Oliver
22. August 2012 13:48
When we started development on Marinas.info, we decided to write acceptance tests for all important features of our application. This decision was even more justified by the fact that a bunch of similar platforms are to follow using the same codebase. We wanted an application with less bugs and easier maintenance. Writing good, automated acceptance tests is not easy and it’s not fast, either. For some time now, we’ve been trying to get the first set of our tests run green, which proved especially tricky on our TeamCity continuous integration server. This post investigates a working solution. The ingredients: SpecFlow, Coypu (Selenium), Browser, Web Server, and MvcIntegrationTestFramework SpecFlow In .NET world, using SpecFlow to write acceptance tests is nothing new and has recently become, yet again, more appealing after its update to version 1.9. One of our scenarios for verifying image upload functionality looks like this: It’s simple to write, easy to read and great living documentation. For Browser based tests you need: Coypu (Selenium) Everyone who has written tests for Selenium for even a mildly ajax-y site knows how painful it can be to create reliably working tests. Coypu alleviates the pain and makes test creation as straight-forward as it should be in the first place. Coypu is: A robust wrapper for browser automation tools on .Net, such as Selenium WebDriver that eases automating ajax-heavy websites and reduces coupling to the HTML, CSS & JS A more intuitive DSL for interacting with the browser in the way a human being would A few examples of Coypu’s clean API can be seen here in one of the step definitions for the above scenario (Browser is an instance of the BrowserSession class from Coypu): A web browser To run browser based tests you, of course, need … a browser! Coypu offers support for quite a bunch of them, including the usual suspects Internet Explorer, Chrome, and Firefox. A web server You need to host your application in some web server or another to process requests. Well, this statement turns out to be only partially true, as you will see with the MvcIntegrationTestFramework. But at least for browser based test you need a web server, and you basically have the choice between IIS and IIS Express (if you don’t want to write your own or use someone else’s implementation). We chose IIS Express as it is manageable through a non-administrator account, but it needs to be installed on all machines that will execute the tests. For non-browser based tests: MvcIntegrationTestFramework Introduced by Steven Sanderson in 2009, this small framework allows to write integration tests for ASP.NET MVC applications and execute them without a browser! It empowers you to make assertions on your controllers’ actions’ results rather than on the rendered html output by injecting some clever hooks into your MVC application under test. An example of how a test would look can be found in the above mentioned post. The “magic” of this framework lies in the use of ApplicationHost.CreateApplicationHost() which creates an application domain for hosting your ASP.NET application. Check out this screenshot of part of the source code: How to put the pieces together After a quite radical evolution of our test code (which you can read up on in my follow-up post The Long Road to Browser Based Acceptance Testing), we finally settled for the following: Before the first test starts, setup an instance of the AUT (application under test). This includes: deploying the AUT as we do for our staging environment, but to a temp folder initialize an AppHost instance à la MvcIntegrationTestFramework, i.e. an ASP.NET enabled application domain that hosts the AUT execute the Orchard setup command via the AppHost instance (instead of running the setup through a browser, which we used to do but was a lot slower) Before each test run (SpecFlow scenario) we then execute various commands to setup the environment for the concrete test, e.g.: clean the database simply by overwriting it with a copy we saved after the initial setup create Marina entries that will be displayed and searchable on the site, again, using the AppHost instance Once we want to execute steps in the browser, we do the following: start an instance of IIS Express pointing to the deployed application (we used the wrapper code from Spinning up IISExpress for integration testing) initiate a Coypu BrowserSession which under the hood creates an instance of the browser you choose after battling with Internet Explorer, Chrome, and Firefox Portable, we now use Firefox 10.0.6 ESR (Extended Support Release) because version 10 is of now the highest version supported by Selenium (2.1.25) and the ESR doesn’t ask to be updated all the time After each test run (SpecFlow scenario) we do this: close the browser shut down the IIS Express instance (we slightly modified the above mentioned wrapper code calling Kill() on the process instance after the call to CloseMainWindow() so that it reliably terminates even on TeamCity) Conclusion Setting up a reliable environment for automatically executing acceptance tests has not been a walk through the park but we finally have a solution that basically “just works”. Hopefully, our experience will help you save a couple of hours and also some headache along the way Happy coding!
by Oliver
16. September 2011 20:06
Lately, I was having trouble debugging certain parts of my code in Visual Studio, and all I wanted to know was the value of some variable at some point in time. Well, I’d use some logging if I could just get at that value easily. But for some objects I don’t really know what I’m looking for or where I should be looking for it. So just give me the values of all the members of that object, will ya? And could you recurse that? But no deeper than 3 levels, alright? Or let’s say… 5? public static string ToDebugString(this object obj, int maxdepth, int depth=0)
{
if (obj == null)
return "null";
if (obj is IConvertible)
return obj.ToString();
if (depth >= maxdepth)
return "...";
var sb = new StringBuilder();
if (depth > 0)
sb.AppendLine();
foreach (var propertyInfo in obj.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance))
{
sb.Append(new string(' ', 2*depth)).Append(propertyInfo.Name).Append(": ");
try
{
var value = propertyInfo.GetValue(obj, new object[0]);
sb.AppendLine(ToDebugString(value, maxdepth, depth + 1));
}
catch (Exception ex)
{
sb.AppendLine(string.Format("[{0}]", ex.Message));
}
}
// remove newline from end of string
var newLine = Environment.NewLine;
if (sb.Length >= newLine.Length)
sb.Replace(newLine, "", sb.Length - newLine.Length, newLine.Length);
return sb.ToString();
}
With this little helper I can now simply call anyobject.ToDebugString(4 /* maxdepth */) and I get a nicely formatted debug view of that object; e.g. Request.Url.ToDebugString(3) gives me:
AbsolutePath: /logg.aspxAbsoluteUri: http://localhost:55235/logg.aspxAuthority: localhost:55235Host: localhostHostNameType: DnsIsDefaultPort: FalseIsFile: FalseIsLoopback: TrueIsUnc: FalseLocalPath: /logg.aspxPathAndQuery: /logg.aspxPort: 55235Query: Fragment: Scheme: httpOriginalString: http://localhost:55235/logg.aspxDnsSafeHost: localhostIsAbsoluteUri: TrueSegments: Length: 2 LongLength: 2 Rank: 1 SyncRoot: Length: 2 LongLength: 2 Rank: 1 SyncRoot: ... IsReadOnly: False IsFixedSize: True IsSynchronized: False IsReadOnly: False IsFixedSize: True IsSynchronized: FalseUserEscaped: FalseUserInfo:
Nice
Right now this method chokes on indexed properties but once I’ll need it I’ll go and look for a way to include them. It also chokes any exceptions on the way to just get the job done.
Happy coding!
by Oliver
6. September 2011 22:18
Recently, we encountered a quite surprising behavior of MSBuild – the continuous integration build of our new collaborative Todo Management app (we hope to go into beta soon!) would produce a broken version whereas the local build with VS2010 was all smooth and well. Our admin and tester already posted about this problem over at his blog: MSBuild does not build like Visual Studio 2010. The exception message finally led me down the right path: Server Error in '/' Application. No constructors on type 'Teamaton.TodoCore.Repositories.TodoRepositoryJson' can be found with 'Public binding flags'. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. Exception Details: Autofac.Core.DependencyResolutionException: No constructors on type 'Teamaton.TodoCore.Repositories.TodoRepositoryJson' can be found with 'Public binding flags'. The TodoRepositoryJson is a type we used at the very beginning of our development to quickly get started using a JSON document as data store. Later we switched to SQLite, so now we have another implementation: TodoRepositoryDb. Both implement the same interface ITodoRepository. Turns out, the Autofac type registering code was the culprit: 1: var builder = new ContainerBuilder();
2: builder.RegisterAssemblyTypes(typeof (Todo).Assembly)
3: .Where(t => t.Name.Contains("Repository"))
4: .AsImplementedInterfaces()
5: .InstancePerLifetimeScope();
What worked with Visual Studio, didn’t work with MSBuild: obviously – well, now it is – both ITodoRepository implementations were registered with Autofac, and while Autofac’s assembly scanning delivered them in the order we assumed from the DLL built with VS – first, TodoRepositoryJson, and second, TodoRepositoryDb, thus overriding the first registration – MSBuild seems to build a DLL which returns the inverse order! Very strange.
Honestly, I’m not familiar with the anatomy of DLLs and surprised by this result. But it’s the only explanation I’ve found so far.
Well, the solution to the problem is, of course, to care more about what we register with Autofac and in which order.
Happy coding,
Oliver
by Anton
22. July 2011 13:37
Bantam Is Quitting Services We as teamaton were using bantam for all of our todos. At the beginning of this year bantam was bought by ConstantContact, and they announced that bantam will cease services as of July 1. Since we are developing our own todo management tool (see our blog), we decided to push the development and use it instead of bantam. Of course we wanted to take all of our todos with us. We used bantams export feature which gave us a JSON-file with all our tasks (closed and open ones). So I took on the task to write an JSON import feature into our tool. Json.NET After a bit of researching, I found that the library Json.NET would suit our import needs perfectly. Applying the deserialization was pretty straightforward – the documentation helped a lot. Here is the code from the Import controller: [HttpPost]
public ActionResult Import(HttpPostedFileBase file)
{
var todos = new List<Todo>();
if (file != null && file.ContentLength > 0)
{
var streamReader = new StreamReader(file.InputStream);
string text = streamReader.ReadToEnd();
streamReader.Close();
var bantamTodos = JsonConvert.DeserializeObject<IList<BantamTodo>>(text) as List<BantamTodo>;
todos = bantamTodos.Select(bantamTodo => bantamTodo.ConvertToTodo()).ToList();
_todoRepository.SaveImport(todos);
}
return RedirectToAction("List");
}
It just opens the file, extracts the content as a string, deserializes the string into a list of bantam todos, and then converts these bantam todos into our “normal” todos.
Indirection Via BantamTodo-Class
As you can see, I did not convert the JSON directly into our Todo-class. You can use attributes and the converter class to deserialize JSON into a class of your liking. There are two reasons, why I did not choose to do so: I did not want to load the Todo-class with attributes and converters, and I thought it would be easier to introduce a middle class (BantamTodo), which poses as a container and converter.
I used a nice tool, to take a good look into the original JSON-file: JSON Viewer.
With the information about the structure of the JSON file I started implementing via the TDD pattern. Here is my test class, which tests the deserialization of the the bantam todos and the conversion from the class BantamTodo to Todo:
[Test]
public void Should_Import_BantamToDo_FromJson()
{
var jsonToDo = ArrangeTaskAsJson();
var bantamToDo = JsonConvert.DeserializeObject<BantamTodo>(jsonToDo);
bantamToDo.Categoy.Should().Be.EqualTo("Organisation");
bantamToDo.Complete.Should().Be.EqualTo(true);
bantamToDo.Created_At.Should().Be.EqualTo(new DateTime(2011, 6, 30, 0, 41, 57));
bantamToDo.Due.Should().Be.EqualTo(new DateTime(2011, 7, 1));
bantamToDo.Author.Name.Should().Be.EqualTo("Anton");
bantamToDo.Assigned_To.Name.Should().Be.EqualTo("Oliver");
bantamToDo.Related_To[0].Name.Should().Be.EqualTo("ToDo-Management Tool");
bantamToDo.Name.Should().Be.EqualTo("Entwicklung nach Gebieten Personen zuordnen - Verantwortliche, Blogs, etc.");
bantamToDo.Description.Should().Be.EqualTo("some good description");
bantamToDo.Flagged.Should().Be.EqualTo(true);
}
[Test]
public void Should_Convert_BantamToDo_ToTodo()
{
var jsonToDo = ArrangeTaskAsJson();
var bantamToDo = JsonConvert.DeserializeObject<BantamTodo>(jsonToDo);
var todo = bantamToDo.ConvertToTodo();
todo.Status.Should().Be.EqualTo(bantamToDo.Complete ? Status.Closed : Status.Open);
todo.Description.Should().Contain(bantamToDo.Name);
todo.Description.Should().Contain(bantamToDo.Description);
todo.Tags.Select(t => t.Name).Should().Contain(bantamToDo.Categoy);
foreach (var bantamProject in bantamToDo.Related_To)
todo.Tags.Select(t => t.Name).Should().Contain(bantamProject.Name);
todo.DateCreated.Should().Be.EqualTo(bantamToDo.Created_At);
todo.DateCompleted.Value.Date.Should().Be.EqualTo(bantamToDo.Due);
todo.DateDue.Should().Be.EqualTo(bantamToDo.Due);
todo.Creator.Name.Should().Be.EqualTo(bantamToDo.Author.Name);
todo.Assignee.Name.Should().Be.EqualTo(bantamToDo.Assigned_To.Name);
todo.Priority.Value.Should().Be.EqualTo(bantamToDo.Flagged ? 2 : 0);
}
The implementation was pretty straightforward. Since it was my first time working with MVC, and also my first time working with JSON, it took me some time. All in all – research, export and meetings included – it took me about 12 hours.
If you have any suggestions as to improvement I would appreciate them. If you are trying to import JSON into .NET yourself, I hope that this article helps.
by Oliver
28. June 2011 01:27
Three year old code: 1: protected string CpeBehaviorIds()
2: {
3: var cpeIds = "";
4:
5: var helpItems = GetHelpItems(divGlobal);
6:
7: foreach (var helpItem in helpItems)
8: cpeIds += helpItem.CollapsiblePanelBehaviorID + ',';
9:
10: // remove comma at end
11: if (cpeIds.Length > 0)
12: cpeIds = cpeIds.Remove(cpeIds.Length - 1);
13:
14: return cpeIds;
15: }
16:
17: protected string CpeExpandIds()
18: {
19: var cpeIds = "";
20:
21: var helpItems = GetHelpItems(divGlobal);
22:
23: foreach (var helpItem in helpItems)
24: cpeIds += helpItem.CollapsiblePanelExpandID + ',';
25:
26: // remove comma at end
27: if (cpeIds.Length > 0)
28: cpeIds = cpeIds.Remove(cpeIds.Length - 1);
29:
30: return cpeIds;
31: }
32:
33: protected static List<HelpItem> GetHelpItems(Control control)
34: {
35: var idList = new List<HelpItem>();
36:
37: if (control is HelpItem)
38: idList.Add(control as HelpItem);
39: else
40: foreach (Control child in control.Controls)
41: idList.AddRange(GetHelpItems(child));
42:
43: return idList;
44: }
New code:
1: protected string CpeBehaviorIds()
2: {
3: return divGlobal.Controls<HelpItem>().Select(h => h.CollapsiblePanelBehaviorID).JoinNonEmpty(",");
4: }
5:
6: protected string CpeExpandIds()
7: {
8: return divGlobal.Controls<HelpItem>().Select(h => h.CollapsiblePanelExpandID).JoinNonEmpty(",");
9: }
10:
11: public static string JoinNonEmpty(this IEnumerable<string> values, string separator)
12: {
13: return String.Join(separator, values.Where(s => !string.IsNullOrEmpty(s)).ToArray());
14: }
LINQ – we love you!
Oliver
P.S. Controls<Type>() is another extension method defined like this:
1: /// <summary>
2: /// Returns all controls of the given Type that are found inside this control.
3: /// Searches recursively.
4: /// </summary>
5: public static IEnumerable<T> Controls<T>(this Control control) where T : Control
6: {
7: var controls = control.Controls;
8:
9: if (controls.Count == 0) return new List<T>(0);
10:
11: var newColl = new HashedSet<T>();
12: foreach (Control child in controls)
13: {
14: if (child is T)
15: newColl.Add((T) child);
16:
17: var childColl = child.Controls<T>();
18: foreach (T ctrl in childColl)
19: newColl.Add(ctrl);
20: }
21:
22: return newColl;
23: }