Gayan
Gayan

Reputation: 2871

Google Dynamic Links with .NET

I'm trying to get google firebase dynamic link work on my .net core project my code is as below

public static async Task<string> GetShortLink(string longLink)
{
    var service = AuthenticateServiceAccount("[email protected]", "Opt/Keys/quallogi-keys.json", new[] { "https://www.googleapis.com/auth/firebase" });
    var request = service.ManagedShortLinks.Create(new CreateManagedShortLinkRequest
    {
        DynamicLinkInfo = new DynamicLinkInfo
        {
            //DynamicLinkDomain = "https://quallogi.page.link",
            DomainUriPrefix = "quallogi.page.link",
            AnalyticsInfo = new AnalyticsInfo(),
            IosInfo = new IosInfo(),
            Link = "https://github.com/distriqt/ANE-Firebase/wiki/DynamicLinks---Create-Dynamic-Links",

        },

        Suffix = new Suffix { Option = "SHORT" },
        Name = "shortlink",


    });
    var response = await request.ExecuteAsync();
    return response.PreviewLink;
}

public static FirebaseDynamicLinksService AuthenticateServiceAccount(string serviceAccountEmail, string serviceAccountCredentialFilePath, string[] scopes)
{
    try
    {
        if (string.IsNullOrEmpty(serviceAccountCredentialFilePath))
            throw new Exception("Path to the service account credentials file is required.");
        if (!File.Exists(serviceAccountCredentialFilePath))
            throw new Exception("The service account credentials file does not exist at: " + serviceAccountCredentialFilePath);
        if (string.IsNullOrEmpty(serviceAccountEmail))
            throw new Exception("ServiceAccountEmail is required.");

        if (Path.GetExtension(serviceAccountCredentialFilePath).ToLower() == ".json")
        {
            GoogleCredential credential;
            using (var stream = new FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read))
            {
                credential = GoogleCredential.FromStream(stream)
                     .CreateScoped(scopes);
            }

            return new FirebaseDynamicLinksService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "Firebasedynamiclinks Service account Authentication Sample",
            });
        }
        else if (Path.GetExtension(serviceAccountCredentialFilePath).ToLower() == ".p12")
        {

            var certificate = new X509Certificate2(serviceAccountCredentialFilePath, "notasecret", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
            var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
            {
                Scopes = scopes
            }.FromCertificate(certificate));

            return new FirebaseDynamicLinksService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "Firebasedynamiclinks Authentication Sample",
            });
        }
        else
        {
            throw new Exception("Unsupported Service accounts credentials.");
        }

    }
    catch (Exception ex)
    {
        throw new Exception("CreateServiceAccountFirebasedynamiclinksFailed", ex);
    }
}

but when i run the code google throws exception

Google.Apis.Requests.RequestError Internal error encountered. [500] Errors [ Message[Internal error encountered.] Location[ - ] Reason[backendError] Domain[global] ]

enter image description here

what was the issue ?

Upvotes: 8

Views: 2064

Answers (1)

Adam Skinner
Adam Skinner

Reputation: 897

I'm a little surprised you got as far as you did. Currently, this library has two issues:

  1. When using ManagedShortLinks, it blows up as described. It only gets so far if you deal with the second issue, however.
  2. The ETag members are undecorated, and get serialized as is. You will see an error like so:
Google.Apis.Requests.RequestError
Invalid JSON payload received. Unknown name "ETag" at 'dynamic_link_info.android_info': Cannot find field.
Invalid JSON payload received. Unknown name "ETag" at 'dynamic_link_info.ios_info': Cannot find field.
Invalid JSON payload received. Unknown name "ETag" at 'dynamic_link_info': Cannot find field.
Invalid JSON payload received. Unknown name "ETag" at 'suffix': Cannot find field.
Invalid JSON payload received. Unknown name "ETag": Cannot find field. [400]

I haven't found a way around ManagedShortLinks. However, ShortLinks will work. I'll show you how I did it.

       public async Task<string> GetDeepLink(Invitation inv)
       {
           var playId = _configurationProvider.GetSetting(AppSettingNames.GooglePlayAppId);
           var iosId = _configurationProvider.GetSetting(AppSettingNames.AppleAppStoreAppId);
           var domain = _configurationProvider.GetSetting(AppSettingNames.GoogleFirebaseDynamicLinkDomain);

           NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);

           queryString["Key1"] = "value1";

           var mslReq = new CreateShortDynamicLinkRequest();
           mslReq.DynamicLinkInfo = new DynamicLinkInfo();
           mslReq.DynamicLinkInfo.AndroidInfo = new AndroidInfo() { AndroidPackageName = playId };
           mslReq.DynamicLinkInfo.IosInfo = new IosInfo() { IosAppStoreId = iosId, IosBundleId = playId };
           mslReq.DynamicLinkInfo.DomainUriPrefix = $"https://{domain}";
           mslReq.DynamicLinkInfo.Link = $"https://www.example.com/?{queryString}";
           mslReq.Suffix = new Suffix() { Option = "SHORT" };
           var json = JsonConvert.SerializeObject(mslReq, Formatting.Indented, new CreateShortDynamicLinkRequestConverter());

           var request = _firebaseDynamicLinksService.ShortLinks.Create(new CreateShortDynamicLinkRequest());

           request.ModifyRequest = message =>
               message.Content = new StringContent(json, Encoding.UTF8, "application/json");

           var res = await request.ExecuteAsync();
           return res.ShortLink;
       }

This depends upon CreateShortDynamicLinkRequestConverter:

    public class CreateShortDynamicLinkRequestConverter : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.NullValueHandling = NullValueHandling.Ignore;
            var t = JToken.FromObject(value);
            var modified = t.RemoveFields("ETag");

            modified.WriteTo(writer);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override bool CanConvert(Type objectType)
        {
            return true;
        }

        public override bool CanRead => false;
    }

which in turn depends upon RemoveFields:

        // source: https://stackoverflow.com/a/31581951/773673
        public static JToken RemoveFields(this JToken token, params string[] fields)
        {
            JContainer container = token as JContainer;
            if (container == null) return token;

            List<JToken> removeList = new List<JToken>();
            foreach (JToken el in container.Children())
            {
                JProperty p = el as JProperty;
                if (p != null && (fields.Contains(p.Name)))
                {
                    removeList.Add(el);
                }
                el.RemoveFields(fields);
            }

            foreach (JToken el in removeList)
            {
                el.Remove();
            }

            return token;
        }

At the end of the day, the big problem here is the lack of decoration of the ETag members. We need to work around that. I believe that customizing BaseClientService.Initializer.Serializer when the service is instantiated with the public NewtonsoftJsonSerializer(JsonSerializerSettings settings) constructor will allow you to specify the Converters to use, but I stopped when I got it working. The real fix for this is to simply decorate the ETag members to not participate in serialization (providing that doesn't break anything else!).

Upvotes: 2

Related Questions