Pages

Showing posts with label Azure. Show all posts
Showing posts with label Azure. Show all posts

Thursday, 11 June 2020

Remote debug Umbraco on Azure Web App

The title is a bit misleading, because you can debug ANY Azure Web App, not just when running Umbraco, but I had to keep it a bit Umbraco related.

When everything works

If everything was always working, I wouldn't be writing about it today. There's plenty of guides to show you how to do remote debugging, when everything is fine.

In my experience - more often than not - there's always some crucial little step, preventing you from quickly attaching a debugger and finding the exact cause of that elusive error that you just can't reproduce on your local environment.
Remote debugging has saved me HOURS of trying to get to that one error that only one of your users is experiencing in production.

Under normal circumstances, you would
  • publish your Web App from Visual Studio in debug mode. 
  • go to your Cloud Explorer, scroll down to your web app
  • click Attach debugger (this will activate remote debugging if not already active)
 

What can possible go wrong?

Well, you could:
  • not be able to publish for some reason
  • have forgotten to put it in Debug mode
  • not see your Web App in Cloud Explorer, even though your seeing everything else and you DO have access in the Azure Portal and IT already verified that everything is working
    (clearly, I'm speaking hypothetically, because this never happens)

Do it all yourself

Enable remote debugger

First, you'll need to enable remote debugging on your Web App.
  • Go to your Web App in the Azure Portal.
  • Go to Configuration > General Settings
  • Enable Remote Debugging and choose your version of Visual Studio

Download the PublishProfile

The PublishProfile contains all endpoints and credentials you'll need for accessing this Web App.
You can download it from the Overview blade.


The file looks like this.
Obviously, I've fudged it a bit, so you wouldn't get any ideas of trying to mess with my app.
<publishData>
	<publishProfile profileName="[yourApp] - Web Deploy" publishMethod="MSDeploy" publishUrl="[yourApp].scm.azurewebsites.net:443" msdeploySite="[yourApp]" userName="$[yourApp]" userPWD="[yourAppPassword]" destinationAppUrl="http://[yourApp].azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
		<databases />
	</publishProfile>
	<publishProfile profileName="[yourApp] - FTP" publishMethod="FTP" publishUrl="ftp://[yourAppFTP].ftp.azurewebsites.windows.net/site/wwwroot" ftpPassiveMode="True" userName="[yourApp]\$[yourApp]" userPWD="[yourAppPassword]" destinationAppUrl="http://[yourApp].azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
		<databases />
	</publishProfile>
	<publishProfile profileName="[yourApp] - ReadOnly - FTP" publishMethod="FTP" publishUrl="ftp://[yourAppFTP]dr.ftp.azurewebsites.windows.net/site/wwwroot" ftpPassiveMode="True" userName="[yourApp]\$[yourApp]" userPWD="[yourAppPassword]" destinationAppUrl="http://[yourApp].azurewebsites.net" SQLServerDBConnectionString="" mySQLDBConnectionString="" hostingProviderForumLink="" controlPanelLink="http://windows.azure.com" webSystem="WebSites">
		<databases />
	</publishProfile>
</publishData>
The interesting bits are 
  • url - this should be "[yourApp].azurewebsites.net" (make sure there's no ".scm." part)
  • userName - make sure to take the fully qualified one : "[yourApp]\$[yourApp]"
  • userPWD

Attach your debugger

  • In Visual Studio, go to Debug > Attach to process (CTRL-ALT-P)
  • In the Connection target, paste your Url plus port 4022 (for VS2017, port 4024 for VS2019)
  • In the popup-window, paste username and password 
If your credentials are correct and there is no company firewall blocking your outgoing request, you should be able to see all running processes on the remote machine.
We want to attach to the w3wp.exe. If running a .Net Core application, this will be dotnet.exe.


And that's it, you are now remote debugging Umbraco (or whatever you're working on) and can put breakpoints where you think the problems occur.

Troubleshooting

Now, there are numerous things in the preceding steps that can go wrong and possibly many more that I haven't even encountered yet myself.

Cannot attach debugger

You get an error after entering the url of the Web App, or the popup asking for credentials does not appear.

It's most likely a connectivity issue.
  • Check your corporate firewall.
  • Check if it works from home (while not on any VPN), or tether via your mobile.
  • Check your Web App for IP filtering.

Cannot activate breakpoints

The version of your code doesn't match the version running on your Web App.
  • Try republishing the latest version.
  • Try finding the source branch that matches this release.
If the problem you're investigating is in a specific class library, try building a new version of that project only and push that .dll and it's accompanying .pdb via Kudu or FTP (you have the ftp-credentials in the PublishProfile you downloaded earlier).

Make a backup of the existing .dll, so you can quickly revert.

Monday, 8 April 2019

Umbraco Courier - Azure Storage

When you use Umbraco Courier to transfer items from development to production or vice-versa, you may run into a small problem when you store your media in AzureStorage, as is highly recommended.

The problem

When transferring media items with Courier, the data and the blob (= the actual image) are transferred to the target environment, but at the target, Umbraco is saving the blob to the default location on disk, not checking its FileSystemProviders, telling it to transfer the blob to AzureStorage.

The solution

I made a small modification to Hugo's solution on the Umbraco forum, where he raises the issue, then solves it himself.

You'll need to provide 3 EventHandlers.

The first one will run on the sending environment, telling the target to queue a postprocessor for the image.

public class MediaEventTrigger : ApplicationEventHandler
{
  protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
  {
    ExtractionManagerEvents.ExtractedItemResources += ExtractedItemResources; ;
  }

  private void ExtractedItemResources(object sender, ItemEventArgs e)
  {
    var provider = sender as RevisionExtraction;

    //make sure the media event only fires for media items, not other resources
    if (e.Item.ItemId.ProviderId == ItemProviderIds.mediapropertyDataItemProviderGuid)
    {
      var media = e.Item.Dependencies.Single(i => i.ItemId.ProviderId == ItemProviderIds.mediaItemProviderGuid);
      provider.Context.ExecuteEvent("QueueCourierMediaEvent", e.ItemId, new SerializableDictionary<string, string>());
    }
  }
}

The second one will be running at the receiving end. It will queue the third, instructing it to move the blob to Azure Storage.

public class MediaEventListener : ItemEventProvider
{
  public override string Alias => "QueueCourierMediaEvent";

  public override void Execute(ItemIdentifier itemId, SerializableDictionary<string, string> parameters)
  {
    ExecutionContext.QueueEvent("UpdateMediaPath", itemId, parameters, EventManagerSystemQueues.DeploymentComplete);
  }
}

And now for the actual moving of the blob.

public class MediaEventQueueListener : ItemEventProvider
{
  public override string Alias => "UpdateMediaPath";

  public override void Execute(ItemIdentifier itemId, SerializableDictionary<string, string> parameters)
  {
    var fs = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();
    var ms = ApplicationContext.Current.Services.MediaService;

    var media = ms.GetById(Guid.Parse(itemId.Id));
    var oldPath = GetMediaPath(media);
    if (!string.IsNullOrEmpty(oldPath))
    {
      var absolutePath = HttpContext.Current.Server.MapPath(oldPath); //path to file on filesystem
      using (var fileStream = System.IO.File.OpenRead(absolutePath))
      {
        fs.AddFile(oldPath, fileStream); //will be picked up by FileSystemProvider and saved to AzureStorage
      }
     Directory.Delete(Path.GetDirectoryName(absolutePath), true);
     }
  }

  private string GetMediaPath(IMedia media)
  {
    //Image may use ImageCropper (or not), so test presence of Json
    var umbracoFile = media.GetValue<string>(Constants.Conventions.Media.File);
    if (!string.IsNullOrWhiteSpace(umbracoFile))
    {
      if (umbracoFile.StartsWith("{"))
      {
        //the umbracofile property contains the src for the image, which at the beginning is the location of the file on the filesystem.
        var umbracoFileJson = JsonConvert.DeserializeObject<ImageCropDataSet>(umbracoFile);
        return $"~{umbracoFileJson.Src}";
      }
      else
      {
        //Contains the path directly
        return umbracoFile;
      }
    }

    return string.Empty;
  }
}

Shouldn't we rename?

No. Or, not really.

Let's say we are transferring media/1028/flower.jpg from Dev to Prod. The path will remain media/1028/flower.jpg, because that just makes life easy.

You could argue that we need to rename each item's folder with a Guid or something, so it becomes unique, but then you would have to write another handler, parsing and updating all your content, as you'd need to update all media-paths in grid-content referencing your media.

If you always migrate in one direction, this becomes a non-issue, as their will be (almost) no name-collisions on the media folder.

Name-collisions?

Suppose someone manually added tree.jpg to Prod directly.
It is possible that - after transfer from Dev - we now have media/1028/flower.jpg and media/1028/tree.jpg in the same folder on Azure storage. I can live with that.

The only scenario where this becomes a problem is, if you uploaded a different flower.jpg and the blob is overwritten when you transfer the one from Dev.

In my case, this is extremely unlikely, but it's good to at least be aware of this possibility.