Filling out the service class with NBuilder queries & Building our UI
What Will I Learn?
- You will learn Filling out the service class with NBuilder queries
- You will learn Building a working UI
Requirements
NBuilder https://github.com/nbuilder/nbuilder
Difficulty
Intermediate
Tutorial Contents
- Filling out the service class with NBuilder queries
- Building our UI
Curriculum
Filling out the service class with NBuilder queries & Building our UI
In the last tutorial we discussed what you could use NBuilder for. We then got a copy of NBuilder and included it into our project. From there we started to set up a couple of projects that would help us to demonstrate the use of NBuilder. We then built up a few files to demonstrate what a backend developer and presentation developer might exchange prior to getting started coding. Specifically we created an interface, a simple domain object, and a mock service class.
In this tutorial we will continue our discussion by filling out the implementation in our service class with some NBuilder code. Once we have the working service class in place we can then create a working UI (in the ASP.NET MVC project we created in the last tutorial).
Filling out the service class with NBuilder queries
In the last chapter we defined an IProductService interface that specified two methods: GetProducts and GetProductByID. The GetProductByID method is pretty easy to understand so we will implement it our ProductServiceMock first.
1. ProductServiceMock.cs
public Product GetProductByID(int productID)
{
//generate a list of products
Product result = Builder<Product>.CreateNew()
.With(p=>p.ProductID = productID)
.Build();
return result;
}
This class uses this NBuilder syntax (starting with the entry point of Builder) and specifies that we will be working with the domain object Product. We then use the CreateNew method to create a single instance of a Product object. And we use the With method to specify that the id of the product should be the same as the id that is passed in (to make it seem like everything is working as expected). The Build command is called once you are done specifying what you want your output to look like. In this case we don't really have any special needs. After asking for a single instance to be created we can then call the Build method and NBuilder will create a new instance of a Product object.
By default all of the properties are filled based on their data types. The product's name will be the class name (Product) and the Name property and the first identity. In this instance it will be "ProductName1".
Intelligent naming
If you think for a moment about this naming strategy it is very predictable. This predictability could almost be counted on. The only reason this name or the key values would change is if you were to refactor your class and or property name. For that reason, counting on these predictable values in a test might seem ok at first. However, if one of your primary reasons for having tests is to have flexible code so that you can frequently and easily refactor (thus changing the values generated by NBuilder) be careful how much you rely upon this dynamic output!
Once we have our GetProductByID method roughed out we can move on to our second method, GetProducts. The GetProducts method asks for the current pageNumber and pageSize which will allow us to implement pagination in our UI (something that we are all probably used to creating in a data grid or similar UI). Given that scenario we need a fairly flexible way of creating fake data so that our UI can be built in the appropriate way. Let's take a look at the method real quick.
2. ProductServiceMock.cs
public List<Product> GetProducts(int pageNumber, int pageSize)
{
pageSize = pageSize >= 5 ? pageSize : 5; //default pageSize to 5 if less than 5
pageNumber = pageNumber >= 1 ? pageNumber : 1; //default pageNumber to 1 if less than 1
//make a paged list of products
List<Product> result = Builder<Product>
.CreateListOfSize(pageSize * pageNumber)
.Build()
.Skip((pageNumber-1 >= 0 ? pageNumber - 1 : 0) * pageSize)
.Take(pageSize)
.ToList();
return result;
}
As you can see, the first couple of lines are fairly common guard clauses to ensure that our input is what we expect. If it is not what we expect then we are setting some defaults. In this case we don't want our pageNumber to be less than one (cause how could someone visit page -5?) and we want our pageSize to be at least 5 items.
From there we have what we need to generate a list of products. Take note that we are populating a generic List of type Product. We once again call into the Builder entry point, specifying that we are working with the type Product. We then call the CreateListOfSize (instead of CreateNew) method and set the length of the list to equal the page size multiplied by the number of pages which will ultimately generate the entire list of items that we would be working with.
With this configuration specified we can then call the final Build method which generates our list. We then call the Skip method (a standard .net method exposed off of most collections) and specify how many records we want to skip by subtracting the page number by one and then multiplying the result by the number of records to be displayed in a page. We then use the Take method to specify the number of records to take from the collection (starting from the first record after the skipped collection of records). For an example if we were on page two with ten records per page (a total collection of twenty records) – we would subtract one from page two (the page we are on) giving us one which we would then multiply by the page size of ten which results in ten – which is the number of records we are too skip. Once we have skipped the first ten records (skipped page one) we can then take the next ten records. All very common if you have done it before...not so common if you haven't!
Building our UI
Now that we have the guts of our mocked product service we can easily stand up an ASP.NET MVC UI on top of this technically working back end. We will do this by first opening up the index.aspx page in the Views/Home directory of our web project. Anywhere in the MainContent area of that page place the following code.
3. Views/Home/Index.aspx
<%
foreach (Product product in (List<Product>)ViewData["Products"])
{
%>
<%= Html.ActionLink(product.ProductName.ToString(), "Detail",new {id=product.ProductID}) %> <br>
<%
}
%>
Notice that in the code above we have a simple foreach loop that is iterating through a collection of products passed to us via the ViewData collection (a common though ugly non-typed magic strings method of passing data around in an MVC app). We then output links with the product's name for display and an ID parameter built into the URL to pass the product's ID.
Then in the HomeController.cs file (in the Controllers directory) place this code into your Index() action.
4. Controllers/HomeController.cs
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
ViewData["Products"] = new ProductServiceMock().GetProducts(2, 10);
return View();
}
In the Index method we are passing the default "Welcome to ASP.NET MVC!" message. We then get a list of products from the ProductServiceMock().GetProducts method. I hard coded the page number and page size for this demo but do notice that we are on the second page of a twenty record collection.
Next we can quickly implement a details page. We do this by creating a new action result on the HomeController. It looks something like this.
5. Controllers/HomeController.cs
public ActionResult Detail(int id)
{
Product p = new ProductServiceMock().GetProductByID(id);
return View(p);
}
Notice that this action result is a tad bit different from the others defined out of the box from the MVC template. Here we have an id parameter in the form of an int. By default this is already configured for us in our RegisterRoutes method of the Global.asax. We will play off of this and expect it to be passed in to us. We can then use this passed in id to get the appropriate Product record from our generated collection.
6. Global.asax.cs (defined out of the box)
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
Once you have this in place you can right click anywhere in the method and select "Add View" from the top of the popup context menu. This will show the Add View wizard which will dynamically create the Details (or any other type of...) view for you.
Once you set the name, check the create strongly-typed view, choose the type you want your view to be typed by, and then select a master page you can click the Add button and automagically a new Product details view is created for you!
7. Views/Home/Detail.aspx
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Detail</h2>
<fieldset>
<legend>Fields</legend>
<p>
ProductID:
<%= Html.Encode(Model.ProductID) %>
</p>
<p>
ProductName:
<%= Html.Encode(Model.ProductName) %>
</p>
<p>
Sku:
<%= Html.Encode(Model.Sku) %>
</p>
<p>
Cost:
<%= Html.Encode(String.Format("{0:F}", Model.Cost)) %>
</p>
</fieldset>
<p>
<%=Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> |
<%=Html.ActionLink("Back to List", "Index") %>
</p>
</asp:Content>
You can run this site again. From the home page (where our previous collection of Products are displayed) you can click on a record and see its details. In this case I am clicking on record 17. Notice that my ID lines up correctly but the other properties don't.
If it is imperative that all of this data lines up you can mock out all of the details using more With methods (actually you only specify one With() method...followed by And() methods). Or you could spin up a collection the size of the ID passed in (plus one) and then pluck the last item off the collection. This would then make all the data line up as you might have expected it.
Summary
In this tutorial we completed our high level look how NBuilder could be used as a stand in while you create the UI for your application. Obviously NBuilder can be used for a great deal of other purposes such as mocking aspects of your code for testing purposes. Also, we barely grazed the surface when it comes to NBuilder functionality.
Posted on Utopian.io - Rewarding Open Source Contributors
Thank you for the contribution. It has been approved.
You can contact us on Discord.
[utopian-moderator]
Hey @cues I am @utopian-io. I have just upvoted you!
Achievements
Suggestions
Get Noticed!
Community-Driven Witness!
I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!
Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x