Op 20 september zijn we te gast bij Unit4 in Sliedrecht. Daar zal Jessy Liberty komen te spreken over het ontwikkelen voor het Windows 8 platform. Mis deze unieke gratis avond niet, en schrijf je in op http://www.dotned.nl De avond begint om 19.00 maar de deuren gaan om 18.00 open voor een hapje en een drankje en uiteraard contact met je mede-ontwikkelaars!
15-9-2012 13:38:19
door
dotNedEvents
in
dotNed Events
Op donderdag 26 april zijn we te gast bij Humiq in Gorinchem. Op deze avond spreekt Michiel van Otegem over Windows Identity Foundation. Schrijf je in op http://www.dotned.nl/register/45/modern-beveiligen-met-windows-identity-foundation-bij-humiq-.aspx
19-4-2012 23:07:27
door
dotNedEvents
in
dotNed Events
Op donderdag 24 november zijn we te gast bij Oosterkamp Training | Consultancy. We hebben wederom een gratis avond waarin Sander Gerz, Thomas Huijer en Dennis Vroegop je laten zien hoe je software bouwt voor het Windows 8 platform met WinRT als runtime. Inschrijven kan op http://www.dotned.nl/register.aspx?meeting=40.
16-11-2011 10:01:35
door
dotNedEvents
in
dotNed Events
Ook dit jaar is er weer een SharePoint Connections. Twee dagen deep dive in alles wat met SharePoint te maken heeft. Veel bekende sprekers uit binnen- en buitenland zullen hier acte de presence geven om jou alles te vertellen wat je moet weten over SharePoint. Enkele bekende namen zijn Asif Rehmani, Micheal Noel, Danny Burlage en uiteraard Mirjam van Olst en Wouter van Vugt. De Keynote zal worden verzorgd door Dan Holme. Holmes was de technical consultant voor de NBC Olympics in Beijing en Va...
8-11-2011 17:16:49
door
dvroegop
in
Dennis' avonturen in .net
Op 27 oktober organiseert dotNed bij 4dotNet in Meppel een avond over HTML5. De spreker Maurice de Beijer zal laten zien hoe een ontwikkelaar gebruik kan maken van de features die HTML5 je biedt. De avond is, zoals altijd, gratis na inschrijving op http://www.dotned.nl. We beginnen om 19.00 uur maar de deur is open vanaf 18.00 voor een hapje en een drankje!
20-10-2011 12:16:58
door
dotNedEvents
in
dotNed Events
How come that companies hire recruiters to find new developer employees for them? Apparently it’s either hard to find them, or they do not want to make the effort to find them their selves. If it’s the latter, it could be because the company is really, really small and they don’t have the resources. But in all other cases, maybe developers should start wondering if they want to work for such a company.
But let’s get back to the first reason; let’s assume it’s hard to find new developers.
Simply for this reason, employers are willing to pay immense amounts of money to recruiters, sometimes tens of thousands of dollars. But when you ask them how often their developers go to conferences, most of the time the answer is that every year only a few can go to the Microsoft TechDays.
Some companies have 5 developers at work and send all of them to the Microsoft TechDays. How can a company with 100 developers employed, tell me without being ashamed that they only send 5 developers to the Microsoft TechDays. When asked, I was told that sending many developers costs a lot of money. But is it so wrong of me, to question their intention? Because if they have 20 times more developers, aren’t they making at least 20 times the money? So it’s obvious that TechDays will costs 20 times as much as well. In other words, it’s all relative and in the end, the company isn’t making more costs than the company with 5 developer employees.
Of course I understand you cannot simply send 100% of your development department to a conference, all at the same time. Your customers will probably not accept that.
Invest in your employees But what I cannot understand is that you’re willing to pay an insane amount of money to a recruiter, but you’re not willing to invest in your employees! What about the idea to send 50% of your developers to the Microsoft TechEd Europe every other year? That way every single developer will visit TechEd every other year, but there’s always 50% of your developers working at your customers. What do you think the result will be?
I know not every developer is like me, but I’d expect every great developer lining up at your doorstep, applying for a job at your company. Especially since you’re probably the first in The Netherlands to support your employees like that. You don’t need recruiters anymore and you can invest the money saved in your own employees.
Want to know why Microsoft and Google have the best developers in the world? Because they provide their developers the best benefits that go around. Free lunch, great hardware, conferences and Google even has slides that end up in the restaurant. Today I read that every seat at the Google office is at most 150 feet of a restaurant or another place where their employees can get something to eat. Simply because that’s where the best discussions are being held and the best ideas are being produced.
Create an atmosphere people feel happy in, it’ll make them more productive When you invest in your employees, it’s not only for them gaining knowledge. It’s also creating an atmosphere where people love to work. This will not cost you much, especially when you consider what it’ll cost to replace someone with the knowledge a developer has. Besides technical knowledge, they also have all the knowledge of the current system and especially of the business domain you’re working in. I once read that a very thorough research was done and that creative employees like developers cost around $50.000 to replace, taking everything into account. You can seriously cut down on costs by investing in your employees!
Again, try to provide a great place to work at. Provide them with great hardware, some gadgets like a Surface RT/Pro tablet, etc. Free lunch works really, really great. And provide them the chance to go to a conference at the very least every other year! Contact me if you’re willing to make me an offer! And I’m not kidding!
Additional informationFor Dutch readers, here's another post on Facebook about recruiters. If you really need to hire a recruitment agency, I have a recommendation there as well... :-)
14-6-2013 23:31:00
door
Dennis van der Stelt
in
Dennis van der Stelt
For SDN Event of June 14th I did two presentations Transactions Why are transactions the bottleneck of so many applications? Why isn't the database always consistent, even though we use transactions? This session explains why everything in a database is a transaction and how a developer should deal with them. Why your software complains that MSDTC isn't running and how the CAP Theorem can help. After this session you'll be able to explain to your DBA why he doesn't understand transactions. Slidedeck here I spoke about messaging, of which you can find more information here What is messaging High Availability (a good article on benefits of messaging) SOLID Principles part 2 Of the SOLID principles, made famous by Robert C. Martin, we'll discuss the Interface Segregation Principle and the Dependency Inversion Principle. This session will explain them thoroughly and give real life examples instead of the regular customer & order examples. You'll walk away knowing what the benefits are and how to use them properly. Slidedeck here Demo code During the presentation the implementation of the Factory was incorrect, as the objects instantiated weren’t done so using the Unity Container. This is fixed in this demo. The EmailMessageSender even uses an additional IResourceRepository to retrieve (faked) translations from the database. If you have questions, don’t hesitate to contact me.
14-6-2013 22:46:30
door
Dennis van der Stelt
in
Dennis van der Stelt
In a particular project for a client I needed to copy all field properties from document A to document B. However the content type should not be changed. For simplicity I ignore edge cases. For example in case the source and destination content type have different required SPFields. At first I copied all available fields from source to destination: foreach (SPField field in sourceItem.Fields){ if (field.ReadOnlyField) { continue; } if (destinationListitem.Fields.ContainsField(field.InternalName)) { destinationListitem[field.InternalName] = sourceItem[field.InternalName]; }}destinationListitem.SystemUpdate()
The content type changed. No wonder as the SPField ‘ContentType’ was copied.Changed code to to ignore SPField ‘ContentType’
IEnumerable<string> ignoreFields = new List<string>(){ "ContentType"}; foreach (SPField field in sourceItem.Fields){ if (field.ReadOnlyField) { continue; } if (ignoreFields.Contains(field.InternalName, StringComparer.InvariantCultureIgnoreCase)) { continue; } if (destinationListitem.Fields.ContainsField(field.InternalName)) { destinationListitem[field.InternalName] = sourceItem[field.InternalName]; }}destinationListitem.SystemUpdate()
But still content type changed.It turns out that you need to skip the SPField ‘ContentType’ and ‘MetaInfo’ to make sure the content type of the destination item is not changed.
Final code snippet:
IEnumerable<string> ignoreFields = new List<string>(){ "ContentType", "MetaInfo"}; foreach (SPField field in sourceItem.Fields){ if (field.ReadOnlyField) { continue; } if (ignoreFields.Contains(field.InternalName, StringComparer.InvariantCultureIgnoreCase)) { continue; } if (destinationListitem.Fields.ContainsField(field.InternalName)) { destinationListitem[field.InternalName] = sourceItem[field.InternalName]; }}destinationListitem.SystemUpdate()
12-6-2013 9:32:00
door
Koen van der Linden
in
Koen van der Linden's SharePoint Blog
This last blog post is to inform you that this blog has moved to a new location: http://www.geertvanhorrik.com Update your bookmarks!
9-6-2013 17:01:38
door
in
Geert van Horrik
In the two previous blog posts I showed how to unit test and ASP.NET WebAPI controller and how to unit test the client side code depending on the HttpClient class. Both unit tests are perfectly valid but as so often just adding unit tests can be deceptive. After all testing an ApiController by just calling the methods makes it perfectly possible to call them in such a way that would never be possible using a real HTTP request. So in order to complete out testing we should test the integration of the client and server parts using an integration test. Integration testing with the ASP.NET WebAPI Creating a complete integration test with the complete HTTP stack is possible but adds rather a lot of complexity to our test harness. It would be much nicer if we could just test the client and server part together without actually having to use an HTTP server. Turns out that this can be done quite easy because the HttpClient can use an HttpMessageHandler as the messaging pipeline and the HttpServer derives from that same HttpMessageHandler. When testing the client code in the previous blog post we already used this fact. However there we added a custom TestingDelegatingHandler<T> to return a fake result message. Instead of doing that we can also just use the real server and configuration and this will create an instance of the ApiController and route the request to that instance. Testing the getting of books The code used to test getting the books from the ApiController is actually quite similar to testing just the client code. The main difference is that the actual controller is instantiated and called so make sure that returns the result to test for. In this case it uses an in memory repository so I know exactly that the same set of 5 books are returned every time. 1: [TestMethod]
2: public void WhenGettingItShouldReturnAllBooks()
3: {
4: // Arrange
5: var config = new HttpConfiguration();
6: WebApiConfig.Register(config);
7: var server = new HttpServer(config);
8: var client = new BooksClient(new HttpClient(server));
9:
10: // Act
11: var books = client.GetBooks();
12:
13: // Assert
14: Assert.AreEqual(5, books.Count());
15: }
Nice and simple and still it uses the almost complete WebAPI stack including the routing.
The test of adding a new book
Adding a new book in an integration test is just as simple:
1: [TestMethod]
2: public void WhenPostingABookItShouldBeAdded()
3: {
4: // Arrange
5: var config = new HttpConfiguration();
6: WebApiConfig.Register(config);
7: var server = new HttpServer(config);
8: var client = new BooksClient(new HttpClient(server));
9:
10: var book = new Book { Title = "A new book", Author = "Maurice" };
11:
12: // Act
13: var response = client.PostBook(book);
14:
15: // Assert
16: var newBook = response.Item1;
17: Assert.AreEqual(book.Title, newBook.Title);
18: Assert.AreEqual(book.Author, newBook.Author);
19:
20: Assert.AreEqual(string.Format("http://localhost:63895/api/books/{0}",
21: newBook.Id), response.Item2.ToString());
22: }
Adding message serialization to the integration tests
Both these test do a pretty good job of integrating the client and server parts for a quick in memory test but still don’t test message serialization and de-serialization. Adding the serialization part to an integration test would make it even more useful. Fortunately with the excellent work by Kiran Challa found in this blog post and the InMemoryHttpContentSerializationHandler listed there is is easy enough to add. The following test used this InMemoryHttpContentSerializationHandler to test if updating an unknown book throws the expected exception. The serialization part is no problem here but it could have been ;-)
1: [TestMethod]
2: [ExpectedException(typeof(HttpRequestException))]
3: public void WhenPuttingAnInvalidBookItShouldThrow()
4: {
5: // Arrange
6: var config = new HttpConfiguration();
7: WebApiConfig.Register(config);
8: var server = new HttpServer(config);
9:
10: var handler = new InMemoryHttpContentSerializationHandler(server);
11: var client = new BooksClient(new HttpClient(handler));
12:
13:
14: var book = new Book { Id = int.MaxValue, Title = "A non existent book", Author = "Maurice" };
15:
16: // Act
17: client.PutBook(book);
18:
19: // Assert
20: Assert.Fail("Should not get here.");
21: }
Sweet :-)
27-5-2013 21:26:14
door
Maurice
in
The Problem Solver
In a previous post I showed how to unit test an ASP.NET WebAPI Controller. But with a REST service there is both a client and a service component. Assuming for a moment the client part is also written in C# we should test that as well. In this case the client application contains the following class to load books from the REST WebAPI controller: 1: public class BooksClient
2: {
3: private readonly HttpClient _httpClient;
4:
5: public BooksClient(HttpClient httpClient)
6: {
7: _httpClient = httpClient;
8: BaseUrl = new Uri("http://localhost:63895/api/books/");
9: }
10:
11: public Uri BaseUrl { get; private set; }
12:
13: public IEnumerable<Book> GetBooks()
14: {
15: var response = _httpClient.GetAsync(BaseUrl).Result;
16: response.EnsureSuccessStatusCode();
17: return response.Content.ReadAsAsync<Book[]>().Result;
18: }
19:
20: public Book GetBook(int id)
21: {
22: var requestUri = new Uri(BaseUrl, id.ToString(CultureInfo.InvariantCulture));
23: var response = _httpClient.GetAsync(requestUri).Result;
24: response.EnsureSuccessStatusCode();
25: return response.Content.ReadAsAsync<Book>().Result;
26: }
27:
28: public Tuple<Book, Uri> PostBook(Book book)
29: {
30: var response = _httpClient.PostAsJsonAsync(BaseUrl.ToString(), book).Result;
31: response.EnsureSuccessStatusCode();
32: var newBook = response.Content.ReadAsAsync<Book>().Result;
33: var location = response.Headers.Location;
34: return new Tuple<Book, Uri>(newBook, location);
35: }
36: }
This class uses the HttpClient to request the data from the service and extracts the books from the body before returning them.
Testing the BooksClient class
If we want to test this class we need to pass in an HttpClient object. This might not sound like a big deal but as this class doesn’t implement an interface we can’t use most of the standard mocking frameworks like Moq to replace the HttpClient with a test fake.
It turns out that this isn’t a big issue though as you can replace the internal pipeline of the HttpClient instead. This is done by passing in an HttpMessageHandler. This HttpMessageHandler is used as the pipeline to send requests and we can completely replace this with our own implementation. The easiest way is by creating a dummy DelegatingHandler and overriding the SendAsync() function to just return a fake response instead of actually doing an HTTP request.
The TestingDelegatingHandler<T> class
Creating a dummy DelegatingHandler isn’t hard but using the TestingDelegatingHandler<T> makes this really easy. The complete code is below and is pretty simple:
1: public class TestingDelegatingHandler<T> : DelegatingHandler
2: {
3: private Func<HttpRequestMessage, HttpResponseMessage> _httpResponseMessageFunc;
4:
5: public TestingDelegatingHandler(T value)
6: : this(HttpStatusCode.OK, value)
7: { }
8:
9: public TestingDelegatingHandler(HttpStatusCode statusCode)
10: : this(statusCode, default(T))
11: { }
12:
13: public TestingDelegatingHandler(HttpStatusCode statusCode, T value)
14: {
15: _httpResponseMessageFunc = request => request.CreateResponse(statusCode, value);
16: }
17:
18: public TestingDelegatingHandler(
19: Func<HttpRequestMessage, HttpResponseMessage> httpResponseMessageFunc)
20: {
21: _httpResponseMessageFunc = httpResponseMessageFunc;
22: }
23:
24: protected override Task<HttpResponseMessage> SendAsync(
25: HttpRequestMessage request, CancellationToken cancellationToken)
26: {
27: return Task.Factory.StartNew(() => _httpResponseMessageFunc(request));
28: }
29: }
The most important function is the SendAsync() which returns a new Task. Did I mention that the WebAPI is completely async enabled? Well it is so we just have to return a new Task that returns the HttpResponseMessage instead of the HttpResponseMessage directly.
Testing the GetBooks() function
The GetBooks() function gets all books from the REST service. A test is pretty simple. The only thing to be aware of is that in order to use the TestingDelegatingHandler we also need to create an HttpServer object and pass in an HttpConfiguration object. Normally an empty HttpConfiguration will be enough.
1: [TestMethod]
2: public void WhenGettingAllBooksTheyShouldBeReturned()
3: {
4: // Arrange
5: var books = new[]
6: {
7: new Book{Id = 1, Author = "Me", Title = "Book 1"},
8: new Book{Id = 2, Author = "You", Title = "Book 2"}
9: };
10: var testingHandler = new TestingDelegatingHandler<Book[]>(books);
11: var server = new HttpServer(new HttpConfiguration(), testingHandler);
12: var client = new BooksClient(new HttpClient(server));
13:
14: // Act
15: var booksReturned = client.GetBooks();
16:
17: // Assert
18: Assert.AreEqual(2, booksReturned.Count());
19: }
Simple enough right?
Testing the GetBook(int id) function
Testing this method is not much harder but we need to test both a positive and a negative result. The positive is just as simple as above:
1: [TestMethod]
2: public void WhenGettingAValidBookItShouldBeReturned()
3: {
4: // Arrange
5: var book = new Book { Id = 2, Author = "You", Title = "Book 2" };
6: var testingHandler = new TestingDelegatingHandler<Book>(book);
7: var server = new HttpServer(new HttpConfiguration(), testingHandler);
8: var client = new BooksClient(new HttpClient(server));
9:
10: // Act
11: var bookReturned = client.GetBook(2);
12:
13: // Assert
14: Assert.IsNotNull(bookReturned);
15: Assert.AreEqual("Book 2", bookReturned.Title);
16: }
The negative case isn’t much harder, all we need to do is make sure our dummy service returns an HTTP 404 Not Found status. With the overloads for the TestingDelegatingHandler<T> this is easy enough. See below:
1: [TestMethod]
2: [ExpectedException(typeof(HttpRequestException))]
3: public void WhenGettingAnInvalidBookItShouldThrow()
4: {
5: // Arrange
6: var testingHandler = new TestingDelegatingHandler<Book>(HttpStatusCode.NotFound);
7: var server = new HttpServer(new HttpConfiguration(), testingHandler);
8: var client = new BooksClient(new HttpClient(server));
9:
10: // Act
11: client.GetBook(-1);
12:
13: // Assert
14: Assert.Fail();
15: }
Nice and simple right?
Testing am HTTP POST action
Testing an HTTP POST action to add a new book is slightly more complex. Not a whole lot but the REST convention is to return both an HTTP 201 Created status as well as the location of the new resource in an HTTP header. For this purpose the TestingDelegatingHandler<T> has an overload where you can just pass in a lambda to create the response. This gives us full flexibility and with that the test is simple enough.
1: [TestMethod]
2: public void WhenPostingABookItShouldBeAdded()
3: {
4: // Arrange
5: var book = new Book { Id = 2, Author = "You", Title = "Book 2" };
6: var testingHandler = new TestingDelegatingHandler<Book>(request =>
7: {
8: var response = request.CreateResponse(HttpStatusCode.Created, book);
9: response.Headers.Location =
10: new Uri(string.Format("http://domain.com/api/books/{0}", book.Id));
11: return response;
12: });
13: var server = new HttpServer(new HttpConfiguration(), testingHandler);
14: var client = new BooksClient(new HttpClient(server));
15:
16: // Act
17: var result = client.PostBook(new Book());
18:
19: // Assert
20: var bookReturned = result.Item1;
21: Assert.IsNotNull(bookReturned);
22: Assert.AreEqual("Book 2", bookReturned.Title);
23:
24: var location = result.Item2;
25: Assert.AreEqual(new Uri("http://domain.com/api/books/2"), location);
26: }
Of course we still need tests for updating existing resources as well as deleting them but with these examples those should be easy enough :-)
Enjoy!
20-5-2013 17:37:21
door
Maurice
in
The Problem Solver
Index: Getting started with AngularJS Creating an AngularJS Controller The AngularJS $scope is not the MVC Model Using repeating elements in AngularJS Filtering the data in an AngularJS ngRepeat element Showing a list of items like in the previous post is nice but if the list is large most users appreciate the possibility to search in it. Turns out that AngularJS has that already build in so it is really simple to do. We are using exactly the same controller as before, all it contains is a list of people. 1: function DemoCtrl($scope) {
2:
3: $scope.people = [
4: { firstName: 'Maurice', lastName: 'de Beijer' },
5: { firstName: 'Jacob', lastName: 'Smith' },
6: { firstName: 'Sophia', lastName: 'Brown' },
7: { firstName: 'Mason', lastName: 'Lee' },
8: { firstName: 'Emma', lastName: 'Wilson' },
9: { firstName: 'Ethan', lastName: 'Martin' },
10: { firstName: 'Emily', lastName: 'Taylor' },
11: { firstName: 'Wiliam', lastName: 'Wong' },
12: { firstName: 'Emily', lastName: 'Campbell' },
13: { firstName: 'Liam', lastName: 'Williams' }
14: ];
15: }
To let the use filter the list we are going to add the AngularJS filter directive.
The change to the markup is minimal. I did change the items to be an ordered list but that is not important. The important part is adding the “| filter:filterText” to the ng-repeat directive. This filters the data in the array to containing the text in the filterText property. And the filter text is the result of the input element I added and data bound using the ng-model directive.
1: <!DOCTYPE html>
2: <html lang="en">
3: <head>
4: <title>AngularJS Demo</title>
5: <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
6: </head>
7: <body ng-app ng-controller="DemoCtrl">
8: Search:
9: <input type="text" ng-model="filterText" autofocus />
10:
11: <ol>
12: <li ng-repeat="person in people | filter:filterText">
13: {{person.firstName}} {{person.lastName}}
14: </li>
15: </ol>
16:
17: <script src="Scripts/angular.js"></script> 1: 2: <script src="App/Controllers/DemoCtrl.js"></script>
18: </body>
19: </html>
Nice and simple, just the way I like it :-)
13-5-2013 9:51:00
door
Maurice
in
The Problem Solver
One of he goals of the ASP.NET WebAPI is to make REST style API controllers more testable than more traditional WCF services where in the past. For the most part that is true but there are cases where an ApiController depends on the actual incoming request and its data and things can become a bit more difficult. Testing a simple ApiController that gets data Suppose we have the following ASP.NET WebAPI Controller with two Get methods, the first returns the complete list of books and the second returns the book with the requested ID. 1: public class BooksController : ApiController
2: {
3: private readonly IBooksRepository _repo;
4:
5: public BooksController()
6: : this(new BooksRepository())
7: {
8:
9: }
10: public BooksController(IBooksRepository repository)
11: {
12: _repo = repository;
13: }
14:
15: // GET api/books
16: public IEnumerable<Book> Get()
17: {
18: return _repo.GetBooks();
19: }
20:
21: // GET api/books/5
22: public HttpResponseMessage Get(int id)
23: {
24: var book = _repo.GetBook(id);
25:
26: if (book == null)
27: {
28: return Request.CreateResponse(HttpStatusCode.NotFound);
29: }
30:
31: return Request.CreateResponse(HttpStatusCode.OK, book);
32: }
33:
34: // Remainder ommitted
Testing the Get() method
The Get() method that returns all books is easy enough to test. There are no dependencies on WebAPI bits, all it does is return a enumeration of books.
1: [TestMethod]
2: public void WhenGettingItShouldReturnAllBooks()
3: {
4: // Arrange
5: var controller = new BooksController();
6:
7: // Act
8: var books = controller.Get();
9:
10: // Assert
11: Assert.AreEqual(5, books.Count());
12: }
No big deal there :-)
Testing the Get(id) method
When getting a specific book things are a bit more complex. The requested Id might not exist and in that case we should return a specific HTTP response status code of 404 NotFound. This results in that the ApiController method uses the Request property to create a new HttpResponseMessage with the specific HTTP status code. If we just call this method we will receive an ArgumentNullException in the CreateResponse() function.
System.ArgumentNullException: Value cannot be null.
Parameter name: request
Just setting the Request property to a new instance of HttpRequestMessage is a start but not quite enough. With just this change you will see an InvalidOperationException with the following message:
System.InvalidOperationException: The request does not have an associated configuration object or the provided configuration was null.
To fix this we need to add a HttpConfiguration object through the Properties dictionary as below:
1: [TestMethod]
2: public void WhenGettingWithAKnownIdItShouldReturnThatBook()
3: {
4: // Arrange
5: var controller = new BooksController
6: {
7: Request = new HttpRequestMessage()
8: {
9: Properties = { { HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration() } }
10: }
11: };
12:
13: // Act
14: var response = controller.Get(1);
15:
16: // Assert
17: Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
18: var book = response.Content.ReadAsAsync<Book>().Result;
19: Assert.AreEqual(1, book.Id);
20: }
Testing for getting with an invalid ID is equally simple:
1: [TestMethod]
2: public void WhenGettingWithAnUnknownIdItShouldReturnNotFound()
3: {
4: // Arrange
5: var controller = new BooksController()
6: {
7: Request = new HttpRequestMessage()
8: {
9: Properties = { { HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration() } }
10: }
11: };
12:
13: // Act
14: var response = controller.Get(999);
15:
16: // Assert
17: Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
18: }
Testing an HTTP PUT operation
Testing an update using an HTTP put is just as simple as a testing a get action. The implementation looks like this:
1: // PUT api/books/5
2: public HttpResponseMessage Put(int id, Book book)
3: {
4: if (!ModelState.IsValid)
5: {
6: return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
7: }
8:
9: var newBook = _repo.UpdateBook(book);
10: return Request.CreateResponse(HttpStatusCode.OK, newBook);
11: }
And the related test goes as follows:
1: [TestMethod]
2: public void WhenPuttingABookItShouldBeUpdated()
3: {
4: // Arrange
5: var controller = new BooksController()
6: {
7: Request = new HttpRequestMessage()
8: {
9: Properties = { { HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration() } }
10: }
11: };
12:
13: // Act
14: var book = new Book() {Id = 1, Title = "New Title", Author = "New Author"};
15: var response = controller.Put(book.Id, book);
16:
17: // Assert
18: Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
19: var newBook = response.Content.ReadAsAsync<Book>().Result;
20: Assert.AreEqual(1, newBook.Id);
21: Assert.AreEqual("New Title", newBook.Title);
22: Assert.AreEqual("New Author", newBook.Author);
23: }
No big surprises there :-)
Adding new data using an HTTP POST action
Testing an HTTP POST used to add a new book is a little, but not much more, involved. The main reason for this is the HTTP convention to return a 201 Created status code and to include the HTTP Location header with an URI where the new resource can be found. The implementation is as follows:
1: // POST api/books
2: public HttpResponseMessage Post(Book book)
3: {
4: if (!ModelState.IsValid)
5: {
6: return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
7: }
8:
9: var newBook = _repo.AddBook(book);
10:
11: var response = Request.CreateResponse(HttpStatusCode.Created, newBook);
12:
13: var uriString = Url.Link("DefaultApi", new { id = newBook.Id }) ?? string.Empty;
14: response.Headers.Location = new Uri(uriString);
15:
16: return response;
17: }
If we try to test this with a similar setup action as the previous tests we will see a KeyNotFoundException below:
System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
Not a very helpful message, what key is not found? The stack trace gives a clue and it turns out we need to configure the HTTP routing on the HttpConfiguration object. Easy enough as just calling WebApiConfig.Register() with the configuration should do the trick.
However this just leads us to an UriFormatException as the Uri.Link() function returns null.
It turns out we also need to add the HttpRouteData to the Request Properties dictionary with the controller name. No big deal but not exactly obvious.
The correct unit test is as follows:
1: [TestMethod]
2: public void WhenPostingANewBookShouldBeAdded()
3: {
4: // Arrange
5: var httpConfiguration = new HttpConfiguration();
6: WebApiConfig.Register(httpConfiguration);
7: var httpRouteData = new HttpRouteData(httpConfiguration.Routes["DefaultApi"],
8: new HttpRouteValueDictionary { { "controller", "Books" } });
9: var controller = new BooksController()
10: {
11: Request = new HttpRequestMessage(HttpMethod.Post, "http://domain.com/api/books/")
12: {
13: Properties =
14: {
15: { HttpPropertyKeys.HttpConfigurationKey, httpConfiguration },
16: { HttpPropertyKeys.HttpRouteDataKey, httpRouteData }
17: }
18: }
19: };
20:
21: // Act
22: var response = controller.Post(new Book()
23: {
24: Title = "A new book",
25: Author = "The author"
26: });
27:
28: // Assert
29: Assert.AreEqual(HttpStatusCode.Created, response.StatusCode);
30: var addedBook = response.Content.ReadAsAsync<Book>().Result;
31:
32: Assert.AreEqual(string.Format("http://domain.com/api/Books/{0}",
33: addedBook.Id), response.Headers.Location.ToString());
34: }
The arrange section might be a bit more verbose but once you know what to add no big deal.
Enjoy :-)
12-5-2013 21:04:31
door
Maurice
in
The Problem Solver
Index: Getting started with AngularJS Creating an AngularJS Controller The AngularJS $scope is not the MVC Model Using repeating elements in AngularJS Filtering the data in an AngularJS ngRepeat element In the previous posts I showed how to get started with AngularJS and use some of the basic AngularJS directives to data bind. In these examples I uses a really simple single element to bind to. However in lots of business cases you really need to display a list of repeating elements. Fortunately this is really easy to do. The AngularJS ng-repeat directive Whenever you need to repeat a specific block of HTML markup for each item in an array the ng-repeat directive is your friend. Take a look at the simple list of people below. The data for this list is contained in an array that is added to the $scope object in the controller. The actual controller is real simple, see the code below. 1: function DemoCtrl($scope) {
2:
3: $scope.people = [
4: { firstName: 'Maurice', lastName: 'de Beijer' },
5: { firstName: 'Jacob', lastName: 'Smith' },
6: { firstName: 'Sophia', lastName: 'Brown' },
7: { firstName: 'Mason', lastName: 'Lee' },
8: { firstName: 'Emma', lastName: 'Wilson' },
9: { firstName: 'Ethan', lastName: 'Martin' },
10: { firstName: 'Emily', lastName: 'Taylor' },
11: { firstName: 'Wiliam', lastName: 'Wong' },
12: { firstName: 'Emily', lastName: 'Campbell' },
13: { firstName: 'Liam', lastName: 'Williams' }
14: ];
15: }
The markup to generate the list in the screenshot is also real simple:
1: <!DOCTYPE html>
2: <html lang="en">
3: <head>
4: <title>AngularJS Demo</title>
5: <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
6: </head>
7: <body ng-app ng-controller="DemoCtrl">
8: <div ng-repeat="person in people">
9: {{person.firstName}} {{person.lastName}}
10: </div>
11:
12: <script src="Scripts/angular.js"></script> 1: 2: <script src="App/Controllers/DemoCtrl.js"></script>
13: </body>
14: </html>
The DIV element containing the ng-repeat, and everything inside it, is simply repeated for every person in the array of people. Simple right :-)
Editing the people
Editing the people in the list is really easy, just add an input control and use the same ng-model directive as before.
1: <!DOCTYPE html>
2: <html lang="en">
3: <head>
4: <title>AngularJS Demo</title>
5: <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
6: </head>
7: <body ng-app ng-controller="DemoCtrl">
8: <div ng-repeat="person in people">
9: <input type="text" ng-model="person.firstName"/>
10: {{person.firstName}} {{person.lastName}}
11: </div>
12:
13: <script src="Scripts/angular.js"></script> 1: 2: <script src="App/Controllers/DemoCtrl.js"></script>
14: </body>
15: </html>
In the screenshot below I am changing Jacob to Bob and as I type in the input element the text behind it is updated immediately.
Enjoy :-)
10-5-2013 21:30:15
door
Maurice
in
The Problem Solver
Working with a slow development machine can be very frustrating. At least, it frustrates me a lot. During the years I have developed software, I gathered some tweaks to get the best performance out of your build machine. Here are the tips. Keep in mind that in most cases, it’s the hard disk that is the bottle neck. Compiling source code requires a lot of small random write access to your disks, so make sure they are as fast as possible. Buy new hardware Most developers tend to forget this, bu...
2-5-2013 22:41:55
door
in
Geert van Horrik