Monday, June 24, 2019

Using the Azure Node SDK to Create/Update an AppService Container

The Azure Node SDK allows developers to manipulate Azure resources via typescript and node. I was recently tasked with using this SDK to create and update an Azure AppService that was based on a container located in an Azure Container Registry. This means that instead of deploying the application code directly, the AppService runs a Docker image container. Doing things this way, you get all the benefits that containers have to offer.

The problem is that while the Node SDK for app services is very robust, the documentation is a bit lacking (for the moment, anyway?). The SDK is broken out into many separate components. The AppService module can be found here: https://www.npmjs.com/package/@azure/arm-appservice.

After much soul-searching, virgin sacrifices and rain dancing, I managed to find the combination of parameter options that allow me to achieve what I'm after. Using this object:

const newSite: Site = { 
   location: [MY LOCATION], 
   name: [MY SITE NAME], 
   kind: 'app,linux,container', 
   type: 'Microsoft.Web/sites', 
   serverFarmId: '[MY SERVER FARM ID]', 
   resourceGroup: [MY RESOURCE GROUP], 
   reserved: true, 
   isXenon: false, 
   hyperV: false, 
   isDefaultContainer: false, 
   siteConfig: { 
      linuxFxVersion: DOCKER|[REGISTRY URL]/[CONTAINER NAME]:[TAG], 
      appSettings: [ 
            {
                'name': 'WEBSITES_ENABLE_APP_SERVICE_STORAGE',
                'value': 'false'
            },
            {
                'name': 'WEBSITE_HTTPLOGGING_RETENTION_DAYS',
                'value': '14'
            },
            {
                'name': 'DOCKER_REGISTRY_SERVER_URL',
                'value': '[MY REGISTRY URL]'
            },
            {
                 'name': 'DOCKER_REGISTRY_SERVER_USERNAME',
                 'value': '[MY REGISTRY USER]'
            },
            {
                 'name': 'DOCKER_REGISTRY_SERVER_PASSWORD',
                 'value': '[MY REGISTRY PASSWORD]'
            } ]
        }
 }

I was able to call createOrUpdate() and successfully create, and then subsequently update a container-based app service.
import { WebSiteManagementClient } from '@azure/arm-appservice';
import { loginWithServicePrincipalSecret } from "@azure/ms-rest-nodeauth";

await loginWithServicePrincipalSecret(appId, secret, domain, 
    async (err, creds) => { 
        const client = new WebSiteManagementClient(creds, subscriptionId); 
        await client.webApps.createOrUpdate( 
             resourceGroupName, 
             siteName, 
             newSite 
        ); 
});
After running this, I was able to view my newly created AppService in the Azure portal (Note the Container settings item under Settings).




Hopefully this post will offer some guidance to those trying to use this really valuable SDK while the documentation gets some love from their developers.

Thursday, October 25, 2012

Day/Month DateTime extension method

Here's a simple extension method for DateTime that displays a date time in the culture-specific format for Day/Month:

public static class DateTimeExtension{
    public static string ToDayMonthString(this DateTime dt, CultureInfo culture){
        var dateString = dt.Date.ToString("d", culture);
        var year = dt.Year.ToString( CultureInfo.InvariantCulture );
        var dateSeparator = culture.DateTimeFormat.DateSeparator;
        var dayMonth = dateString.IndexOf(year, StringComparison.Ordinal) == 0
                       ? dateString.Replace(year + dateSeparator, string.Empty)
                       : dateString.Replace(dateSeparator + year, string.Empty);
        
        return dayMonth;
    }
}

Hope you find this useful.

Sunday, October 14, 2012

My great Azure adventure

What started out as a quick setup in Windows Azure ended in deployment frustration. My intent was to take my existing ASP.NET MVC 4 application off my current shared hosting plan and subversion for source control and  move it to Azure and TFS. After hearing all the hype, I figured it was at least worth looking into.

Setting up the account
Thanks to Microsoft BizSpark, I was able to take advantage of a free Azure account for a year. This was very easy (though I wasn't thrilled with the fact that I had to enter a credit card number for a free account), and within a few minutes I was looking at my dashboard.

Moving off of subversion to TFS
This was *much* easier than I thought it was going to be. Turns out, all that's needed here is to right-click on the project root folder, and select 'Export' from the context menu. Select the same folder, and it prompts you  with a warning that it will remove all the svn folders, thus removing the binding to subversion.

Moving the database to Azure
Again, super easy. Turns out, in SQL Server 2012, there's an option for 'Deploy database to SQL Azure'. After providing the connection string info, it was a few clicks through a wizard and done.

Publishing the MVC site to Azure
This is where things started to get messy. I deployed the site to Azure, and during the deployment, I was met with a 'Failed to open url . Exception: Class not registered' error, but then the deployment continued with the final output message being 'Success'. So I thought 'whatever, maybe that was nothing terribly important'. I went to navigate to my site and was met  with a Security Exception. Um, huh? I was expecting perhaps a database error in the event that perhaps a permission didn't get carried over, but this wasn't even getting that far. So, I looked into the stack trace. Ah, right. My custom Membership Provider was writing to the event log on error. Probably frowned upon in this environment. Fine. A quick tweak to the code and I was able to see the actual database error - a missing stored procedure. A quick update to the database and the site was up and running. 

So that's not so bad. That's where it sits now. The remaining task is to switch my DNS to point to the new Azure site. This part might be a deal breaker for now anyway. In order to get my 'naked' url sans the 'www' to work (Scott Hanselman's term, not mine: http://www.hanselman.com/blog/MovingAWebsiteToAzureWhileAddingContinuousDeploymentFromGit.aspx) I'm going to have to pay a bit. Not that I'm cheap, but, well, I'm cheap. And I'm supposed to be getting this service through the Microsoft BizSpark program. This is an excellent program, by the way. But my complaint is this. Why would I want to launch a web site and not be able to access the site without the user entering 'www' in the url?

Conclusion
At this point, the jury is still out. what will make my decision for me is the extent to which I'm reminded that I'm developing the app to be deployed to Azure. I just want to build my MVC app using the tools with which I'm familiar. Managing Azure is not something I want to be constantly doing. I'll do a follow up in a week or two with more observations. Or, you can follow me on Twitter (@vinbrown2) to check my latest rant.

Friday, September 21, 2012

Localized Data Annotations without Resource Files

Data annotations are a simple, out-of-the-box way to get validation messages into your web application. And on a small scale, there's a minimal amount of coding to get this to work. For an asp.net MVC 4 application, this mean including jquery.validate.unobtrusive.js and adding the data annotation attributes to your class.  For example:
public class Thingy{
    
    [Required]
    public string Name{ get; set; }

    [Required]
    public string Description{ get; set; }

}
So when the user goes to input a new Thingy, if they leave out the Name or Description, we would like the application to not allow the postback and show them a message indicating that those fields are required. In this case, a default message 'The Name field is required' will be displayed.
However things start to get complicated when localization comes into play. Suppose we want this message displayed in Spanish, or Italian. The common way to handle this is with resource files. And again, there is out-of-the-box support for this. Simply provide a resource name as follows, and it just works.
[Required(ErrorMessageResourceName = "LocalizedResource")]
[StringLength(30, ErrorMessageResourceName = "AnotherResource")]
public string Name { get; set; }
A quick search on "data annotations multilingual error message" will provide more than enough details on how to get that to work.
What happens, though, if resource files aren't an option? What if you get your multilingual content from a database? One way to solve this problem is with a custom ModelValidatorProvider. Below is some sample code to get this to work.
public class LocalizedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
    protected override IEnumerable GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable attributes)
    {
        var dependencyResolver = DependencyResolver.Current;

        var _contentProvider = dependencyResolver.GetService();
        var translatedMessages = _contentProvider.GetContent();

        foreach (RequiredAttribute attribute in attributes.Where(a => a is RequiredAttribute).ToList())
        {
            string myMessage = //Get your content here

            attribute.ErrorMessage = myMessage;
        }

        IEnumerable validators = base.GetValidators(metadata, context, attributes);

        return validators;
    }
}

And to get all this to work, you need to register your provider in global.asax:
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);

            ModelValidatorProviders.Providers.Clear();
            ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider());

        }

Hope this is helpful.