Using UnityWebRequest in Editor Tools

Overall, I’m pretty happy with the UnityWebRequest API for sending and receiving RESTful HTTP requests. One issue I’m running into, however, is using it to access a web API at edit-time (say, in a custom editor tool).

For my use case, we want to convert some ScriptableObject assets into JSON and send them to our server through a REST API using POST.

UnityWebRequest.Send() returns an AsyncOperation, which you can “yield on” in a Coroutine to asynchronously retrieve the result. However, there’s no way to run Coroutines in an editor tool, correct? So, would I just need to block until the AsyncOperation returned from Send() says it’s done? But would it ever say it’s done if I blocked like that? Do I need to send the request from a separate thread in this case?

It seems like the UnityWebRequest could easily support an asynchronous callback mechanism (perhaps the Send() call has an override that takes a callback, which is called when the request completes). But barring that, I’m not aware of an easy/effective way to make use of an AsyncOperation API in the editor. Any suggestions?

As an alternative, I’m looking at the C# HttpWebRequest class, buuuut it would be cool if the UnityWebRequest could work.

1 Like

Hey,

Ever figure out how to use REST in editor?

Thanks,
jrDev

@crafTDev , I guess the silence on this question indicates that there isn’t a good way to use UnityWebRequest in editor…whoops.

I ended up using the .NET HttpWebRequest class instead:

// Create the POST request.
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(destinationUrl);
        request.Method = "PUT";

        // Content type is JSON.
        request.ContentType = "application/json";

        // Fill body.
        byte[] contentBytes = new UTF8Encoding().GetBytes(data);
        request.ContentLength = contentBytes.LongLength;
        request.GetRequestStream().Write(contentBytes, 0, contentBytes.Length);

        try
        {
            using(HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                Debug.Log("Publish Response: " + (int)response.StatusCode + ", " + response.StatusDescription);
                if((int)response.StatusCode == 200)
                {
                    SetEnvironmentVersion(version);
                }
            }
        }
        catch(Exception e)
        {
            Debug.LogError(e.ToString());
        }
1 Like

This may not be an elegant answer, but you can block based on Unity - Scripting API: Networking.UnityWebRequest.responseCode.

responseCode returns -1 until the Send operation is completed.

using(UnityWebRequest www = UnityWebRequest.Get(url)) {
    www.Send();

    while (www.responseCode == -1) {
        //do something, or nothing while blocking
    }
    if(www.isError) {
        Debug.Log(www.error);
    }
    else {
        //Show results as text
        Debug.Log(www.responseCode.ToString());
        //process downloadHandler.text
    }
}
1 Like

I wrote a small blog post on how to use UnityWebRequest to make web calls from the editor, it may help. Its a non-blocking way to use UnityWebRequest.

5 Likes

There is a bug introduced in Unity 5.5 where UnityWebRequests calls will not work on Editor (except if the editor is on Play mode, as in, Playing an scene or game).

Is reported as (Case 858932) UnityWebRequest on Editor only works on Play Mode.

However more than a week passed since the report, and no news from Unity yet.

I found the bug but I can’t view the page. Is there any progress on fixing it?
https://unity3d.com/search?refinement=issues&gq=858932

@erich202 they have merged the issue with another one and closed my report. Is supossedly being worked. I have been updating on this issue here: Google Sheets For Unity

1 Like

That bug is still present and I beleive this is the issue you were looking for: Unity Issue Tracker - [UnityWebRequest] DownloadHandlerScript gets paused if scene is not played

I have a webrequest attached to a button in an EditorWindow, it doesnt log errors but always return an empty string.
If I go int play mode before pressing the button, then it works fine.

Update:
If you put the attribute [UnityEngine.ExecuteInEditMode] on top of your EditorWindow class it will make it work. I’m just not sure if I want to have my script run in edit mode all the time.

@tcz8 heya, thanks for the link to the report. I have long moved to later Unity releases. Right now using 2018.3.2f1 and it works right on the editor using the EditorApplication.update method described in the blog.

Hi, here is an easier implementation that works from 5.6 to 2019.

public static Utility
{
   public static void       StartBackgroundTask(IEnumerator update, Action end = null)
   {
       EditorApplication.CallbackFunction   closureCallback = null;

       closureCallback = () =>
       {
           try
           {
               if (update.MoveNext() == false)
               {
                   if (end != null)
                       end();
                   EditorApplication.update -= closureCallback;
               }
           }
           catch (Exception ex)
           {
               if (end != null)
                   end();
               Debug.LogException(ex);
               EditorApplication.update -= closureCallback;
           }
       };

       EditorApplication.update += closureCallback;
   }
}

Usage :

Utility.StartBackgroundTask(Test());

Where Test() can be :

private IEnumerator   Test()
{
   using (UnityWebRequest w = UnityWebRequest.Get("https://www.google.com"))
   {
       yield return w.SendWebRequest();

       while (w.isDone == false)
           yield return null;

       Debug.Log(w.downloadHandler.text);
   }
}
11 Likes

That’s great man. Thanks a lot for sharing.

1 Like

It’s great! Thanks

2 Likes

Hello,

Just what I needed!

Thanks,
jrDev

It’s possible to use completed event from AsyncOperation

            var req = UnityWebRequest.Get("http://google.com");
            var op = req.SendWebRequest();
            op.completed += (aop) =>
            {
                Debug.Log("completed: " + req.downloadHandler.text);
                req.Dispose();
            };
3 Likes

Unity just released Editor Corutines Utility on the Package Manager, now is possible to do something like this:

using System.Collections;
using Unity.EditorCoroutines.Editor;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;


// https://docs.unity3d.com/Packages/com.unity.editorcoroutines@1.0/api/Unity.EditorCoroutines.Editor.EditorCoroutineUtility.html
public class TestScript : EditorWindow
{

    [MenuItem("Test/Test")]
    public static void ShowWindow()
    {
        GetWindow<TestScript>("Open Window");
    }

    private void OnGUI()
    {
        if (GUILayout.Button("Test Corutine"))
        {
            EditorCoroutineUtility.StartCoroutine(GetJSON(), this);
        }
    }

    protected IEnumerator GetJSON()
    {
        using (UnityWebRequest www = UnityWebRequest.Get("http://localhost/"))
        {
            yield return www.SendWebRequest();

            if (www.isNetworkError || www.isHttpError)
            {
                Debug.LogError(www.error);
            }
            else
            {
                Debug.Log(www.downloadHandler.text);
            }
        }
    }
}
1 Like

Having to import a package for such a simple feature… That’s freaking sad…

2 Likes

I’ve tried the two methods above, and I recommand Editor Corutines Utility. Because somtimes the first method doesn’t work. And I can’t find why ? that’s sad.

I use callback to handle it.

           _postRequest = new UnityWebRequest(apiAddress, "POST");
            var asyncOp = _postRequest.SendWebRequest();
            asyncOp.completed += OnPostRequestCompleted;
private void OnPostRequestCompleted(AsyncOperation op)
        {
            var postRequest = (op as UnityWebRequestAsyncOperation).webRequest;
            if (postRequest.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(postRequest.error);
                _postRequest.Dispose();
            }
           else
           {
               _postRequest.Dispose();
            }
         }