Notes for creating a UWP Surface App using .NET Core

These notes are more for personal use, so sorry for any incoherence :P. But maybe someone will find them helpful, even if UWP apps have been discontinued. Most of this should actually be .NET Core specific except probably the first part. Most of these notes were taken around December, 2016, so are almost certainly out of date.

Creating and uploading to the app store from VS 2015

  • Download VS 2015
  • Downloading certificate

    • Package.appxmanifest > Packaging > Publisher > Choose Certificate > Make a new test cert > OKAY
    • Project > App Store > Associate with App Store
    • Project > App Store > Create Package > Create New
    • Then sign in with the right user
    • Need an admin user that can run programs
  • When creating your app bundle, if it runs into certification problems with the pre-launch process, it’s probably okay to upload the app anyways, that is apparently a known issue, and your app should get certified.

SQLite

Issues

  • Data annotations - where the hell does it say how to import them? Nowhere. I had to look into the EntityFrameworkCore tests.
  using System.ComponentModel.DataAnnotations;
  using System.ComponentModel.DataAnnotations.Schema;
  • Turns out they don't even work. You have to use the DatabaseContext method and define everything separately.

Resources

Form Binding

  • There is some weird caching or something that is happening for the values. So when you try to check the field on every KeyUp event, you see an old value for a while or until you check it manually in the Immediate Window. Very bizarre. So you end up having to add a property to update the value as shown here. Very unintuitive.

Resources

HTTP Requests + JSON

Issues

  • For some reason HTTP GET requests are cached pretty aggressively, so if you're querying an API list you need to make sure the cache is turned off or you'll see the same response over and over.

Testing

  • Get this error when running tests in the Microsoft framework - "the application called an interface that was marshalled for a different thread"

  • Testing HttpClient

    • Great Github discussion - turned out to be for System.Net.HttpClient instead of Windows.Web.Http.HttpClient
    • Microsoft Fakes
    • This was a huge pain. I forget why simply mocking out the HttpClient wasn't a great option. But essentially, the HttpClient has this filter object that you have to mock. Here's what that looks like for me:
/**
 * Adapted from http://stackoverflow.com/questions/36772945/uwp-windows-web-httpclient-fake-for-unit-test
 */
public class MockHttpFilter : IHttpFilter  
{
    public HttpStatusCode expectedStatus { get; set; }
    public IHttpContent expectedContent { get; set; }

    public MockHttpFilter(HttpStatusCode expectedStatus, IHttpContent content)
    {
        this.expectedStatus = expectedStatus;
        this.expectedContent = content;
    }

    public void Dispose()
    {
        // Nothing to dispose
    }

    public IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request)
    {
        var responseMessage = new HttpResponseMessage(this.expectedStatus) { RequestMessage = request };
        responseMessage.Content = expectedContent;
        return DownloadStringAsync(responseMessage);
    }

    private IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> DownloadStringAsync(HttpResponseMessage message)
    {
        return AsyncInfo.Run(delegate (CancellationToken cancellationToken, IProgress<HttpProgress> progress)
        {
            progress.Report(new HttpProgress());
            return Task.FromResult(message);
        });
    }
}

and here's how it's used in a test. You'll just need to inject the http client into whatever controller or class you need it in.

// set http response
IHttpContent content = new HttpStringContent(  
    "{" +
        "\"Objects\": [{" +
            "\"Uuid\": \"Testuuid\"," +
            "\"Name\": \"Changed Name\"" +
        "}]" +
    "}");
var filter = new MockHttpFilter(HttpStatusCode.Ok, content);  
var client = new HttpClient(filter);  
  • Testing in general:
    • Started getting error messages about not being able to test without creating a new unit test project. Problem is that the unit test project they were referring to doesn't work for UWP apps.

Environment-specific Configuration

Update - 2018-03-07 If you have the privilege of working with .NET Core 2.0, there is a much better system for this. I wrote a post about it here.

More of a pain than I thought it would be..
I'm not super happy with this solution - I don't think it allows for enough granularity (eg. a staging environment, or multiple devs). I'm guessing the answer lies somewhere near environment variables, but this solution worked for this project.

What I've tried

  • Tried creating some classes that inherit from a common base class, but my understanding of polymorphism is apparently not so hot, and none of the properties were overridden from the base class. Could probably have used functions instead of properties, but that's getting pretty heavy.
  • Tried setting an environment variable in UnitTestApp.xaml.cs, but for some reason the app always crashed before it started up.
  • Tried setting a conditional directive (TESTING) at the beginning of UnitTestApp.xaml.cs, but that didn't seem to effect which settings class was selected, or the base settings class was still being used.
  • Tried creating a build configuration Test and setting a variable TESTING, but that yielded the same thing as above.

What worked

Not sure if this is the best route, but it worked. You need to create an interface with all of your settings, which will be implemented by your prod settings. Other environments will inherit from there. At least, inheritance is my preference. I'm not sure yet about storing secrets. I'll add that in later.

Then you need a monad to tie it all together and provide access (SettingsService).

    public interface ISettings
    {
        string SERVER { get; }
        string RAVEN_DSN { get; }
    }

And here are your prod settings, which end up being the basis of all your other settings. Anything that needs changing in other environments need to be specifically overridden.

public class ProdSettings : ISettings  
    {
        public virtual string SERVER {
            get
            {
                return "https://my.url.com/";
            }
        }

        public virtual string RAVEN_DSN
        {
            get
            {
                return "iwishsentryworkedbetterwithc#";
            }
        }
    }

And an example of your dev settings:

public class DevSettings : ProdSettings  
    {
        public override string SERVER
        {
            get
            {
                return "http://localhost:9000/";
            }
        }

        public override string RAVEN_DSN
        {
            get
            {
                return "https://ireallyiwshsentryworkedbetterwithc#";
            }
        }

And here's how I tied it all in. This class works for prod and dev because there's nothing I could find to detect a test. For testing, I ended up manually setting the settings.

public class SettingsService  
    {
        private static SettingsService _instance;
        private ISettings _settings;
        private bool _inDebugMode = false;

        private SettingsService() { }

        public static SettingsService Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new SettingsService();
                }

                return _instance;
            }
        }

        public ISettings GetSettings()
        {
            if (_settings == null)
            {
                if (IsInDebugMode())
                {
                    _settings = new DevSettings();
                } else
                {
                    _settings = new ProdSettings();
                }
            }

            return _settings;
        }

        public void SetSettings(ISettings settings)
        {
            _settings = settings;
        }

        public bool IsInDebugMode()
        {
            _isInDebugMode();
            return _inDebugMode;
        }

        [Conditional("DEBUG")]
        private void _isInDebugMode()
        {
            _inDebugMode = true;
        }

    }

Here's how I inject the test settings:

namespace ProjectTests {  
    sealed partial class App: Application {
        ...
        protected override void OnLaunched(...) {
            ...
            SettingsService.Instance.SetSettings(new TestSettings());
        }
    }
}

Easy! Then you call get like so:

Dsn ravenDsn = new Dsn(SettingsService.Instance.GetSettings().RAVEN_DSN);

Future reference/Possible routes

Logging

Resources