Author avatar

I am Jack Histon. My career would not be what it is today without dedication and hard work from software bloggers. My purpose is to give back to that online community. Here I write about programming, software development, architecture, and everything in-between.

  • Ideas of an Imposter

    Tuesday, 14 November 2017

    The Imposter Syndrome is like the fog of war:

    The term seeks to capture the uncertainty regarding one's own capability, adversary capability, and adversary intent during an engagement, operation, or campaign.

    This definition is in regards to military operations. However, the definition can be applied to the imposter syndrome as well. Like most psychological states, it makes us see life without a clear and logical view. It makes us ignorant of our own capabilities, stops us seeing clearly or finishing goals, and overall, provide less business value.

    The Imposter Syndrome is prevalent within the software industry. There seems to be many articles discussing the phenomenon. Even with the vast amount of material, I don't find them personal to my situation. So I thought I would create an article that reveals how I deal with the problem of being an "imposter".

    As a programmer, I feel like an imposter all the time. I think it is very easy to forget past work, always striving for the next problem to solve. The first step to ridding yourself of it is to understand that it is there, and to catch the symptoms.

    Reflection

    Reducing the Imposter Syndrome relies on understanding its root cause. Not understanding the cause is like rowing a boat without a paddle, you have no direction, and will fail in removing the problem.

    On a daily basis, I am still plagued with perfectionism and low confidence. Perfectionism sounds almost positive, who would not want a pefect solution? Obviosly, perfection is unnattainable, and so you spend long hours on tasks with little business value. Both my perfectionism and low confidence are much less than when I started my career, but it is only with being self-conscious of my thought patterns that I have been able to stop them re-ocurring. The first hurdle is identifying those patterns.

    There was a point in my career where I would try and solve problems on my own. I would spend an excessive amount of time on a problem, trying to reach a point where I was satisfied with my solution. I was avoiding failure. Perfectionism will make solving problems time consuming, and will make you feel solutions are never quite complete. This is the definition of perfectionism. You try and solve the problem yourself, but cause problems along with it. Failing is hard, but it is a lesson that people need to go through.

    To solve a problem within engineering, you have to believe in your ability to solve it. The quality of code can be affected by your composure. If you are having a bad day, or are suffering from anxiety, then it will affect your ability to think. If you have low confidence in your ability, then it will paralyse you.

    No Industry Standards

    A big reason why the imposter syndrome is so common in our industry, is the lack of standardisation. Mechanical engineering is one of the oldest of the engineering disciplines. There is a vast amount of regulation and standards for practicing mechanical engineering. Beyond the obvious benefits of the regulations, they are also a fantastic way to help you know when a project is complete.

    A bridge's design can vary wildly from case to case, but every inch of it has to be planned and executed with precision. It is rare to find a software design process that has covered every part of a solution. So why can't the regulations and standards of mechanical engineering be applied to software?

    Explaining the process of building software is always done with metaphors. People explain physical things such as building a house, a bridge, or even carpentry. Unfortunately, software is definitely not like building a physical object. Software is not tangible in this way, and the measurements of success for mechanical engineering do not apply in software.

    The infancy of the software industry is to blame. Software development is maturing at a rapid pace, but during this time, there is a lag which opens and causes misunderstanding about our own ability. Our ability to measure success will improve with the maturity of our profession. Without a formal measurement of success, it is impossible to measure our own ability with an international standard.

    What You Can Do Now

    There are a few techniques that I have found help me tackle the Imposter Syndrome. The first is getting out of my comfort zone. This can involve using a new programming language at work, Using a new development methodology such as Test Driven Development, or even public speaking or a challenging new job. It does not have to be programming to take you out of your comfort zone. The goal of this is not to make you feel uncomfortable, but to improve your perspective on software development as a whole. Having a better perspective on the industry reduces the chance of developing the imposter syndrome (and helps you and your career).

    The second point to make, and this is sounds like an obvious one, is that you're not alone. It is obvious to say that many of us suffer from the Imposter syndrome, but what many people fail to utilise is the team around them. We are not independent workers, that is, trying to win a gold star from superiors makes us forget the big picture. If you are trying to be a guru, a source of knowledge that every person comes to for help, then you are trying to achieve the wrong goal.

    A team should take ownership for every ticket in the backlog. A team should have clear goals, and work towards them together to achieve business value. If a goal is owned by multiple people, then it mitigates the fear of the imposter syndrome. One person should not have sole responsibility for a goal. If you start figuring problems out together, then both developers can stop perfectionism, and give a solid view on what needs to be achieved.

    The Imposter Syndrome strikes when you can not measure your own ability. Therefore, why not think of past achievements that identify your ability. If I am suffering from low confidence, then clearly defining your past achievements can trigger a positive reaction. We tend to remember the past through filters. We sometimes put emphasis on positives or negatives based on our current mood, and this can lead to a better or worse outlook. If we force ourselves to realise our past achievements, then it can give us a confidence boost, and help us achieve our goals. Reviewing achievements sounds simple, but I implore your to try it, as this takes conscious effort to actually think these thoughts. Eventually, the feelings come with those thoughts, and your low confidence will slowly rise.

    Summary

    The Imposter Syndrome is a big topic to cover in one post. What I have tried to do is give pointers on how I deal with it personally. Unfortunately, only some or none of these things may work for you, as dealing with the problem is personal. All I hope is that if you do suffer from the imposter syndrome, that you at least try these techniques, and hopefully come up with your own.

    From someone who has been there, I hope this article helps you know where to start in tackling your imposter. I hope this helps.

  • Automating The Arrange Phase With AutoFixture

    Thursday, 02 November 2017

    In a previous article, we refactored a unit test in two ways. The builder and fixture patterns encapsulated an object's creation, which simplified the arrange phase of a unit test.

    These two patterns produced a lot of boiler-plate code. It would be nice to never have to create an arrange phase in the first place.

    AutoFixture tries to produce unit tests with no arrange phase. As the AutoFixture GitHub page describes:

    AutoFixture is an open source library for .NET designed to minimize the 'Arrange' phase

    Reducing the arrange phase of a unit test is a powerful idea. It allows the unit test to become more readable, and concentrates the unit test on what is being tested.

    How Can AutoFixture Help?

    The purpose of a unit test's arrange phase is to generate auxiliary classes that are used by the system under test. So primarily, AutoFixture is concerned with the generation of primitive and complex types:

    
    var fixture = new Fixture();
    
    // Creating strings
    var str = fixture.Create<string>();
    
    Console.WriteLine(str);
    
    // Output: dbbfdba4-4542-45ea-a15f-d26437af25a2
    
    

    We can see in the example that the main class to use with AutoFixture is the Fixture class itself. We use the fixture to create a primitive type of string. By default, this produces a GUID cast to a string.

    
    var fixture = new Fixture();
    
    // Creating numbers
    var num = fixture.Create<int>();
    
    Console.WriteLine(num);
    
    // Output: 133
    
    

    This example shows the generation of an int primitive.

    These are very simple cases of needing to generate random test data, but what about more complex types?

    
    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        public override string ToString() => $"Id: {Id}, Name: {Name}";
    }
    
    var fixture = new Fixture();
    
    // Creating complex types
    var person = fixture.Create<Person>();
    
    Console.WriteLine(person);
    
    // Output: Id: 108, Name: Name1b3bd547-ae34-4a28-8f75-0e807f073413
    
    

    The power of AutoFixture can be seen here. Using reflection, we have automatically generated a dummy object of type Person.

    AutoFixture can create lists of data:

    
    // Manipulating lists
    var list = new List();
    fixture.AddManyTo(list);
    
    Console.WriteLine(string.Join($"PERSON: {Environment.NewLine}", list));
    
    // Output:  Id: 159, Name: Name591a8bf9-1479-4506-b584-c1d187cc4284PERSON:
    //          Id: 239, Name: Nameefad8d54-9ed1-4e8f-b72e-5adaa2573c44PERSON:
    //          Id: 207, Name: Name0613681d-19d2-4112-bc90-167714525015
    
    

    You can specify the length, as well as how to randomise the data.

    Using AutoFixture With xUnit

    Out of the box, AutoFixture provides an easy way to integrate itself with xUnit:

    
    [Theory, AutoData]
    public void ShouldDoSomethingWithAPerson(Person person)
    {
        ...
    }
    
    

    This will autogenerate the variable person of type Person. Rather than having an arrange phase, we have shortened the unit test to only needing an act and assert phase.

    Summary

    Arranging a unit test can be improved with popular design patterns, but AutoFixture removes the need for arrangement entirely. Adding AutoFixture to your toolbox will make unit tests easier to understand, and more importantly, more maintainable.

    AutoFixture can be used in seeding data, performance analysis, user interface demonstrations, test driven development, and much more.

  • Extending The Razor View Engine With View Location Expanders

    Tuesday, 24 October 2017

    In a previous post, I dived into view results, and how the razor view engine executes a razor view. Part of the post discussed a feature called view location expanders.

    An expander does what its name describes. It expands on locating a view within your application, that is, a way to change how your .cshtml files are located.

    View Localisation

    A good example of a view location expander, is the built-in LanguageViewLocationExpander. It allows the view engine to search for culture-aware views. To use the language view expander, you can add it using startup configuration:

    
    services.Configure<RazorViewEngineOptions>(
        options => options.ViewLocationExpanders.Add(
            new LanguageViewLocationExpander(
                LanguageViewLocationExpanderFormat.SubFolder)));
    
    

    With the expander enabled, searching for the typical Home -> Index view will search in the following locations:

    
    Views/Home/en/Index
    Views/Home/Index
    Views/Shared/en/Index
    Views/Shared/Index
    
    

    The ASP.NET team have also provided a helper method for this expander:

    
    services
        .AddMvc()
        .AddViewLocalization(
            LanguageViewLocationExpanderFormat.SubFolder);
    
    

    This achieves the same as the previous example.

    Adding localisation to our views, means that we can serve different content and hard-coded text based on the locale of our user. This is very powerful, as website content can be focused for specific cultures.

    A Custom Example

    Creating your own view location expander is straight-forward. It requires the implementation of the IViewLocationExpander interface:

    
    public interface IViewLocationExpander
    {
        IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations);
    
        void PopulateValues(ViewLocationExpanderContext context);
    }
    
    

    The interface declares two methods. The PopulateValues method gives us a chance to take the current context and extract data from it. The ViewLocationExpanderContext class itself provides the action context, and so we can retrieve all the associated data for a request. The ExpandViewLocations method allows us to manipulate and extend existing view locations.

    The following is an example of a location expander that checks for a route value with key "alt". If a value exists, then it searches within the folder named after the route value:

    
    public class AlternativeViewLocationExpander : IViewLocationExpander
    {
        private const string ValueKey = "alt";
    
        public IEnumerable<string> ExpandViewLocations(
            ViewLocationExpanderContext context,
            IEnumerable<string> viewLocations)
        {
            context.Values.TryGetValue(ValueKey, out var value);
    
            if (string.IsNullOrWhiteSpace(value))
            {
                return viewLocations;
            }
    
            return ExpandViewLocationsCore(viewLocations, value);
        }
    
        private IEnumerable<string> ExpandViewLocationsCore(IEnumerable<string> viewLocations, string value)
        {
            foreach (var location in viewLocations)
            {
                yield return location.Replace("{0}", value + "/{0}");
                yield return location;
            }
        }
    
        public void PopulateValues(ViewLocationExpanderContext context)
        {
            context.Values[ValueKey] = context.ActionContext.RouteData.Values[ValueKey]?.ToString();
        }
    }
    
    

    The populate values method uses the context to retrieve the route data value. If the value does not exist, then the value will evaluate to null.

    The ExpandViewLocations method does nothing if no value was specified. The folder name searched for is the value of the route data. Given a route value of "alt-views", the following view locations will be searched by the razor view engine:

    
    /Views/Home/alt-views/Index.cshtml
    /Views/Home/Index.cshtml
    /Views/Shared/alt-views/Index.cshtml
    /Views/Shared/Index.cshtml
    
    

    To use this view location expander, you can add it to the RazorViewOptions class, similar to the default localisation expander:

    
    services.Configure<RazorViewEngineOptions>(
        options => options.ViewLocationExpanders.Add(
            new AlternativeViewLocationExpander()));
    
    

    Summary

    With a simple implementation, we have extended the razor view engine, and provided a unique way of finding views.

    Location expanders are useful for multi-tenancy, localisation, and context-sensitive view lookup.

    What other use-cases can you think of?

  • MSBuild Maintenance Made Easy With Shared Properties

    Thursday, 12 October 2017

    The New MSBuild

    MSBuild is the build engine for .NET and Visual Studio. It is written in such a way to be composable, allowing you to combine project files. If you have ever used Visual Studio, then you have indirectly used MSBuild. When you open Visual Studio, it is using MSBuild to restore, build, and manage your projects.

    Recently, a new version of MSBuild has hit the streets. This came out along with Visual Studio 2017, and has changed design. Project files have been simplified, and developers are encouraged to edit them directly.

    For .NET Core projects, Visual Studio now allows the direct editing of project files, without having to unload the project first. This is a big step for Visual Studio advocates.

    A lot of systems can have dozens of projects in one solution. Synchronising package references, target frameworks, and metadata across all these projects can be hard. However, MSBuild is composable, and so we can make our lives easier.

    Declaring A Project File

    Let us define a very simple .csproj file:

    
    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netcoreapp2.0</TargetFramework>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
      </ItemGroup>
    
    </Project>
    
    

    Here we are targeting the .NET Core 2.0 framework, and have a package reference to ASP.NET Core 2.0.

    This is the new MSBuild format used alongside .NET Core, and is only compatible with Visual Studio 2017 version. Luckily for us, Microsoft have provided CLI tooling alongside the new MSBuild system. To restore our packages, just run:

    
    $ dotnet restore
      Restoring packages for msbuildexample.csproj...
      Generating MSBuild file .\obj\msbuildexample.csproj.nuget.g.props.
      Generating MSBuild file .\obj\msbuildexample.csproj.nuget.g.targets.
      Restore completed in 3.21 sec for .\msbuildexample.csproj.
    
    

    We now have a fully functioning MSBuild project file. But what if we have multiple project files? how can we keep all ASP.NET Core versions synchronised between them?

    Declaring a Property

    Let's modify our file, and add a property to it:

    
    <Project Sdk="Microsoft.NET.Sdk.Web">
    
      <PropertyGroup>
        <TargetFramework>netcoreapp2.0</TargetFramework>
        <AspNetCoreVersion>2.0.0</AspNetCoreVersion>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.All" Version="$(AspNetCoreVersion)" />
      </ItemGroup>
    
    </Project>
    
    

    Two areas have changed. Firstly, we have defined a new property "AspNetCoreVersion". We are using the syntax $(AspNetCoreVersion) to use this value. This results in exactly the same as what we achieved previously.

    Using Multiple Files

    When the complexity of a software system begins to rise, we start working with multiple projects. Two or more ASP.NET Core applications that make up a software system can share MSBuild properties by sharing a common.props file:

    
    <!-- common.props file -->
    <Project>
      <PropertyGroup>
        <AspNetCoreVersion>2.0.0</AspNetCoreVersion>
      </PropertyGroup>
    </Project>
    
    <!-- project1.csproj file -->
    <Project Sdk="Microsoft.NET.Sdk.Web">
    
      <Import Project=".\common.props" />
    
      ...
    
    </Project>
    
    <!-- project2.csproj file -->
    <Project Sdk="Microsoft.NET.Sdk.Web">
    
      <Import Project=".\common.props" />
    
      ...
    
    </Project>
    
    

    In the example, there is now a .props file. Both project1 and project2 share this file by importing it as a project reference with this line:

    
      <Import Project=".\common.props" />
    
    

    Now we can change the version of the ASP.NET Core framework in all of our projects at once. Now all we have to do is change this common.props file version, and all of our projects will update in unison.

    Summary

    Projects can end up in a configuration nightmare. It can be laborious to update every project file within one solution with the latest dependency versions. What we can do is create a common.props file to house all of our dependency versions, and save us from dependency hell.

    Due to MSBuild's composable nature, we do not have to stop at just properties. We can also compose project files for common targets (steps in the build process), and even move an entire dependency into the shared project file (and a lot more).

    I hope this helps.

  • ASP.NET Core 2.0 - Repository Overview: Action Results

    Sunday, 08 October 2017

    In This Series

    1. ASP.NET Core MVC - Repository Overview: Model Binding
    2. ASP.NET MVC Core - Repository Overview: Value Providers
    3. ASP.NET Core 2.0 - Repository Overview: Action Discovery
    4. ASP.NET Core 2.0 - Repository Overview: Action Selection
    5. ASP.NET Core 2.0 - Repository Overview: Razor Pages
    6. ASP.NET Core 2.0 - Repository Overview: Action Results

    Contents

    Introduction

    This series tries to explain the underlying mechanisms of ASP.NET Core in a unique way. Instead of only introducing main features, it digs deep into the ASP.NET Core GitHub repository, giving you insight on how it all works. This instalment will discuss action results, taking a deep dive into the popular view result implementation.

    What is the View Result

    In ASP.NET Core, and in traditional MVC, there are many different types of action results. Some of the most popular are:

    The ViewResult is the main action result for rendering razor views to the http response body. It is the interaction point for a routing handler to invoke your views, and complex enough to dedicate its own article to.

    Returning a View Result

    When creating a controller action, we often return a view result:

    
    public ViewResult Index()
    {
        return View();
    }
    
    

    The View() method above will call the following on the Controller class:

    
    [NonAction]
    public virtual ViewResult View()
    {
        return View(viewName: null);
    }
    
    ...
    
    [NonAction]
    public virtual ViewResult View(string viewName, object model)
    {
        ViewData.Model = model;
    
        return new ViewResult()
        {
            ViewName = viewName,
            ViewData = ViewData,
            TempData = TempData
        };
    }
    
    

    This will eventually create a ViewResult class for us, demonstrated in the second method.

    A view result is one example of an IActionResult. IActionResult's are responsible for processing the result of an action. The IActionResult's signature looks like the following:

    
    public interface IActionResult
    {
        Task ExecuteResultAsync(ActionContext context);
    }
    
    

    A very simple signature, with one key method: The ExecuteResultAsync method.

    Executing a View Result

    As described in previous articles of this series, the system will have a list of action descriptors to be ran. The MvcRouteHandler and MvcAttributeRouteHandler are the main IRouter implementations to kick this off. These handlers will be called by routing and given a route context.

    Once the action selection process has finished, these handlers will generate an action invoker (in our case the ControllerActionInvoker), and give it a newly created action context.

    N.B. The ResourceInvoker class is responsible for the MVC action invocation pipeline. It chooses when things should be ran, and at what point the execution of the action result should occur. More can be read here.

    ASP.NET MVC Core Result Execution

    In the above diagram, we can see the call hierarchy from the route handler, all the way down to the call of the ExecuteResultAsync method.

    The Executor

    For each type of view result, there is also a corresponding IActionResultExecutor. So, the view result will call ExecuteAsync on this class:

    
    public override async Task ExecuteResultAsync(ActionContext context)
    {
        ...
    
        var executor = context
            .HttpContext
            .RequestServices
            .GetRequiredService<IActionResultExecutor<ViewResult>>();
        await executor.ExecuteAsync(context, this);
    }
    
    

    Finding and Compiling your View

    The following image shows the execution path of find and compiling a razor view, after the view result is executed:

    ASP.NET MVC Core Find and Compile View

    The first job of the ViewResultExecutor class is to find the specific view specified. This is the first point at which the RazorViewEngine class is used.

    N.B. The ViewResultExecutor class uses either the IViewEngine instance registered in dependency injection, or one specified on your view result if any. This means you can assign a view engine at runtime to your view result.

    You can register your own view engines with the dependency injection system. The first view engine to return a result will be used. This logic can be seen in the CompositeViewEngine.

    Due to us not specifying a view name, the razor view engine will have to find our view using the current action context.

    View Location Expanders

    With the help of IViewLocationExpander interfaces, the engine populates values for the view name, controller name, area name, and page name:

    
    var expanderContext = new ViewLocationExpanderContext(
        actionContext,
        pageName,
        controllerName,
        areaName,
        razorPageName,
        isMainPage);
    Dictionary expanderValues = null;
    
    if (_options.ViewLocationExpanders.Count > 0)
    {
        expanderValues = new Dictionary(StringComparer.Ordinal);
        expanderContext.Values = expanderValues;
    
        // Perf: Avoid allocations
        for (var i = 0; i < _options.ViewLocationExpanders.Count; i++)
        {
            _options.ViewLocationExpanders[i].PopulateValues(expanderContext);
        }
    }
    
    

    View location expanders are a great way to extend the functionality of locating views. For example, the framework itself provides a language view location expander. This means that we can provide views such as Index.en-US.cshtml, to serve a view specific to a locale. These can be added at startup through the AddViewLocalization extension method.

    View Locator Cache

    Using the view location expanders, along with the current action context, we create a ViewLocationCacheKey. This key is used to identify a ViewLocationCacheResult. If there is no cache entry, a ViewLocationCacheResult is created through the OnCacheMiss method in the engine.

    The first step is to find the view location formats specified in the system. These are defined in the global RazorViewEngineOptions and configured in its setup class:

    
    public void Configure(RazorViewEngineOptions options)
    {
        ...
    
        options.ViewLocationFormats.Add("/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        options.ViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
    
        options.AreaViewLocationFormats.Add("/Areas/{2}/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        options.AreaViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        options.AreaViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
    }
    
    

    These are the default locations for views the system uses. The placeholders {0} and {1} are filled out by the current context-e.g., the controller's name, or the current culture.

    In our very first example, we had a standard action in a controller. This means the view location formats will be chosen.

    For each view location, the view, controller, and area names are filled in, and the path is resolved. For example, with the controller "Home", and action "Index", we will have the following view locations by default:

    
    /Views/{1}/{0}          -> /Views/Home/Index
    /Views/Shared/{0}       -> /Views/Shared/Index
    
    // With localisation expander activated:
    /Views/{1}/en/{0}       -> /Views/Home/en/Index
    /Views/Shared/en/{0} or -> /Views/Shared/en/index
    /Views/{1}/{0}.en       -> /Views/Home/Index.en
    /Views/Shared/{0}.en    -> /Views/Shared/Index.en
    
    

    The first location to be able to generate a viable page - i.e., a .cshtml file exists at the relative path - will be chosen as the cache result.

    Generating an IRazorView

    We are trying to find the first location that generates us a new razor page cache result. But how does the razor page get generated? through the use of a IRazorPageFactoryProvider.

    Based on the relative path given - e.g., /Views/Home/Index - the factory will try and generate a razor page. As can be seen in the diagram above, the factory will use the RazorViewCompiler, to generate an IRazorView instance.

    Cache Invalidation

    The view compiler itself has its own cache. The cache is invalidated by file provider expiration tokens. This means that if you change a .cshtml file, you invalidate the cache. This is why you do not have to re-build your application in order to modify a razor view:

    
    // A file exists and needs to be compiled.
    compilationTaskSource = new TaskCompletionSource();
    foreach (var importItem in _templateEngine.GetImportItems(projectItem))
    {
        cacheEntryOptions.ExpirationTokens.Add(_fileProvider.Watch(importItem.FilePath));
    }
    cacheEntry = compilationTaskSource.Task;
    
    

    The view compiler will use the templating engine found in the Razor GitHub repository to generate your view. This is outside the scope of this article.

    The RazorViewEngine will also find any view start files that it needs to compile - i.e., any _ViewStart.cshtml that are found in the hierarchy. These will then be added to the cache result.

    Once the factory has generated us the razor page, and the view start pages, this is added to the view engine cache. A RazorView class is then instantiated - i.e., an IRazorView implementation - and returned to the ViewResultExecutor as a ViewEngineResult.

    Rendering the Razor Page

    The following diagram show the execution path of rendering the razor view, after the lookup and compilation phases:

    ASP.NET MVC Core View Rendering

    Now we have the razor view compiled, we need to render this to the http response. This is achieved in the base class ViewExecutor of the ViewResultExecutor class:

    
    using (var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding))
    {
        ...
    
        viewContext.Writer = writer;
    
        await view.RenderAsync(viewContext);
    
        ...
    }
    
    

    Here we can see the executor call the RazorView instance. The view will then render the view start and main page files.

    As the view start files were already found, these are looped over and executed:

    
    for (var i = 0; i < ViewStartPages.Count; i++)
    {
        var viewStart = ViewStartPages[i];
        context.ExecutingFilePath = viewStart.Path;
    
        ...
    
        await RenderPageCoreAsync(viewStart, context);
    }
     
    

    The RenderPageCoreAsync will call ExecuteAsync on the razor page, rendering and writing it to the http response body.

    After this, it will render the main page, and the layout pages, to the current body stream.

    Creating a Custom Action Result

    Now that we have knowledge on what an action result is, we can create our own.

    All we have to do is implement the IActionResult interface, and return that from our action.

    This could be advantageous in scenarios which require complicated http responses. For example, you might be interacting with an api that posts callbacks, and these callbacks could provide the api with extra metadata in the response body.

    Here is an example that returns lorem ipsum text to the body stream:

    
    public class LoremIpsumActionResult : ActionResult
    {
        public async override Task ExecuteResultAsync(ActionContext context)
        {
            var response = context.HttpContext.Response;
    
            response.ContentType = "text/plain";
    
            using (var streamWriter = new StreamWriter(response.Body))
            {
                streamWriter.Write("Lorem ipsum dolor sit amet, vim iudico utroque complectitur id." +
                    " Graecis quaestio euripidis vis an. Dictas voluptua salutatus sed an," +
                    " mnesarchum posidonium eos at. Pro ad latine accusam," +
                    " epicurei invenire ocurreret ex nec, unum similique adolescens vel an." +
                    " Nam adolescens incorrupte argumentum in.");
    
                await streamWriter.FlushAsync();
            }
        }
    }
    
    

    This is a trivial example, and there is really no reason why you would hide this away in an action result. However, this example highlights what action results can give you: an easy way to modify the http response.

    Summary

    In this article, we have stepped through the code of the ASP.NET Core GitHub repository, exploring how a view result transforms from a razor view, to a http response body.

    Action results are the best way to modify the output of an action. They can perform post-processing, such as turning objects into JSON, e.g., the JsonResult object, or provide a way to stream files given through your own code.

    This article has revealed the innards of the ASP.NET Core github repository, and given insight into how you can extend and customise the discovery and render view phases.


© 2017 - Jack Histon - Blog