Wednesday, 19 July 2017

ASP.NET MVC4 Web API Body Gotcha

If you use Visual Studio 2012 to create a new ASP.NET MVC4 Web API you will be able to create Controller classes pre-populated with stub methods for GET, POST, PUT and DELETE. They'll look something like this...

public HttpResponseMessage Put(string id, [FromBody]string value)
{
}

The trouble is, while they'll happily build and deploy they don't actually work in practice!

Why?

Because the [FromBody] attribute tells Web API to stream the HTTP body into the variable it's about to create. The default variable is "value" and is of type System.String. And Web API has no way to stream any HTTP content types into a string. To help further, some content types will throw an error saying no MediaTypeFormatter is present while other content types will simply process the body and send NULL into the variable.

The trick is to create a new MediaTypeFormatter which recognises the content type text/plain and streams it into a string. Create a new class in your project and add the following text...

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace RESTfulServices.Helpers
{
    public class TextMediaTypeFormatter : MediaTypeFormatter
    {
        public TextMediaTypeFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));
        }

        public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
        {
            var taskCompletionSource = new TaskCompletionSource<object>();
            try
            {
                var memoryStream = new MemoryStream();
                readStream.CopyTo(memoryStream);
                var s = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
                taskCompletionSource.SetResult(s);
            }
            catch (Exception e)
            {
                taskCompletionSource.SetException(e);
            }
            return taskCompletionSource.Task;
        }

        public override bool CanReadType(Type type)
        {
            return type == typeof(string);
        }

        public override bool CanWriteType(Type type)
        {
            return false;
        }
    }
}

Now you make this new formatter available to your API by adding the following line to the bottom of your Global.asax file...

GlobalConfiguration.Configuration.Formatters.Insert(0, new RESTfulServices.Helpers.TextMediaTypeFormatter());

All that's left to do is to send your HTTP message with the following header information...

Content-Type: text/plain

This will load the entire body into your "value" string variable, ready for you to parse as appropriate.



Monday, 17 July 2017

Unit Testing SQL Databases

So in VS2015 it seems to create database projects at version 11.0 but it creates the database unit test project at version 14.0 which means the tests don't work during the build. The error you'll receive is...

Error Message:
Assembly Initialization method GPFacade.UnitTests.SqlDatabaseSetup.InitializeAssembly threw exception. Microsoft.Build.Exceptions.InvalidProjectFileException: Microsoft.Build.Exceptions.InvalidProjectFileException: The imported project "C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v11.0\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" was not found. Confirm that the path in the <Import> declaration is correct, and that the file exists on disk. C:\Agent\_work\6\s\GPFacade\GPFacade.sqlproj. Aborting test execution.
Stack Trace:
at Microsoft.Build.Shared.ProjectErrorUtilities.ThrowInvalidProject(String errorSubCategoryResourceName, IElementLocation elementLocation, String resourceName, Object[] args)
at Microsoft.Build.Shared.ProjectErrorUtilities.ThrowInvalidProject(IElementLocation elementLocation, String resourceName, Object arg0)
at Microsoft.Build.Evaluation.Evaluator`4.ExpandAndLoadImports(String directoryOfImportingFile, String importExpressionEscaped, ProjectImportElement importElement)
at Microsoft.Build.Evaluation.Evaluator`4.EvaluateImportElement(String directoryOfImportingFile, ProjectImportElement importElement)
at Microsoft.Build.Evaluation.Evaluator`4.PerformDepthFirstPass(ProjectRootElement currentProjectOrImport)
at Microsoft.Build.Evaluation.Evaluator`4.Evaluate()
at Microsoft.Build.Evaluation.Evaluator`4.Evaluate(IEvaluatorData`4 data, ProjectRootElement root, ProjectLoadSettings loadSettings, Int32 maxNodeCount, PropertyDictionary`1 environmentProperties, ILoggingService loggingService, IItemFactory`2 itemFactory, IToolsetProvider toolsetProvider, ProjectRootElementCache projectRootElementCache, BuildEventContext buildEventContext, ProjectInstance projectInstanceIfAnyForDebuggerOnly)
at Microsoft.Build.Evaluation.Project.ReevaluateIfNecessary(ILoggingService loggingServiceForEvaluation)
at Microsoft.Build.Evaluation.Project.Initialize(IDictionary`2 globalProperties, String toolsVersion, String subToolsetVersion, ProjectLoadSettings loadSettings)
at Microsoft.Build.Evaluation.Project..ctor(String projectFile, IDictionary`2 globalProperties, String toolsVersion, String subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings)
at Microsoft.Data.Tools.Schema.Sql.UnitTesting.MSBuildWrapper.DeployDatabaseProject(FileInfo databaseProjectFile, String configuration, String targetDatabaseName, String connectionString, Boolean forceDeployment)
at Microsoft.Data.Tools.Schema.Sql.UnitTesting.SqlDatabaseTestService.DeployDatabaseProject(String databaseProjectFileName, String configuration, String providerInvariantName, String connectionString, Boolean forceDeployment)
at Microsoft.Data.Tools.Schema.Sql.UnitTesting.SqlDatabaseTestService.DeployDatabaseProject()
at GPFacade.UnitTests.SqlDatabaseSetup.InitializeAssembly(TestContext ctx) in C:\Agent\_work\6\s\GPFacade.UnitTests\SqlDatabaseSetup.cs:line 20

To get round this...


  1. Right mouse click on the database project and select "Unload"
  2. Right mouse click on the unloaded database project and select "Edit"
  3. Search the file for a section of text which looks like this...

  <PropertyGroup>
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
    <!-- Default to the v11.0 targets path if the targets file for the current VS version is not found -->
    <SSDTExists Condition="Exists('$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets')">True</SSDTExists>
    <VisualStudioVersion Condition="'$(SSDTExists)' == ''">11.0</VisualStudioVersion>
  </PropertyGroup>



  1. Replace the 11.0 with 14.0 and save the file
  2. Right mouse click on the unloaded database project and select "Reload"
  3. Commit your changes and check they now build