I'm getting following error while integrating facebook api over Windows 8.1 for store apps:-
Given URL is not permitted by application configuration......"
Screenshot showing error dialog:
Please help to resolve this issue.
private FacebookSession _session;
private FacebookClient _client;
private string _accessToken;
string appID = "749110128499368";
Dictionary<string, object> parameters = new Dictionary<string, object>{
{"response_type","token"},
{"display","touch"},
{"redirect_uri","https://www.facebook.com/connect/login_success.html"},
};
FacebookClient oauth;
string[] extended_permissions;
async void getToken()
{
try
{
FacebookSessionClient fbClient = new FacebookSessionClient(appID);
if (ApplicationData.Current.LocalSettings.Values.ContainsKey("fbaccesstoken"))
{
_accessToken = ApplicationData.Current.LocalSettings.Values["fbaccesstoken"].ToString();
}
else
{
_session = await fbClient.LoginAsync("user_about_me,read_stream");
_accessToken = _session.AccessToken;
ApplicationData.Current.LocalSettings.Values["fbaccesstoken"] = _accessToken;
}
_client = new FacebookClient(_accessToken);
}
catch (Exception e){
extended_permissions = new string[] { "user_friends", "email" };
oauth = new FacebookClient() { AppId = appID };
if (extended_permissions != null && extended_permissions.Length > 0)
{
var scope = new StringBuilder();
scope.Append(string.Join(",", extended_permissions));
parameters["scope"] = scope.ToString();
}
getLogin();
}
}
void getLogin()
{
oauth.Version = "2.2";
extended_permissions = new string[] { "user_friends", "email" };
oauth = new FacebookClient() { AppId = appID };
if (extended_permissions != null && extended_permissions.Length > 0)
{
var scope = new StringBuilder();
scope.Append(string.Join(",", extended_permissions));
parameters["scope"] = scope.ToString();
}
var loginUrl = oauth.GetLoginUrl(parameters);
fbBrowser.Navigate(loginUrl);
}
async private void fb_browser_Navigated(object sender, NavigationEventArgs e)
{
FacebookOAuthResult fb_result;
var fb = new FacebookClient();
string uri = e.Uri.AbsoluteUri;
Uri uri_fb = new Uri(uri);
if (fb.TryParseOAuthCallbackUrl(uri_fb, out fb_result))
{
if (fb_result.IsSuccess)
{
_accessToken = fb_result.AccessToken;
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,()=>
{
GetData(_accessToken);
});
}
else
{
var errordescription = fb_result.ErrorDescription;
var errorreason = fb_result.ErrorReason;
MessageDialog m = new MessageDialog(errordescription + "\n" + errorreason);
await m.ShowAsync();
}
}
}
async void GetData(string access_token)
{
string uriString = "https://graph.facebook.com/me?access_token=" + access_token;
HttpClient wClient = new HttpClient();
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,()=>
{
Task gettingData = wClient.GetStringAsync(uriString);
});
}
Related
I can't use the Twilio SDK in Microsoft Dynamics 365 (Twilio library is not installed in Dynamics and can't include the dll in my plugin registration) so I've had to do a http post using the HttpClient. The call to Twilio happens successfully because Twilio is able to send me an verification email but the breakpoint after PostAsync never gets hit, nor does an exception get caught. I need to capture the output from the PostAsync. What am I doing wrong?
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public class TwilioMessageInput
{
public string To { get; set; }
public string Channel { get; set; }
}
public class TwilioMessageOutput
{
public string Message { get; set; }
}
private void button1_Click(object sender, EventArgs e)
{
// https://www.twilio.com/docs/verify
// https://www.twilio.com/docs/verify/email
string url = "https://verify.twilio.com/v2/Services/VA********************************/Verifications/";
string authToken = "AC********************************:********************************"; //-u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN
string email = "***************#************.com";
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("To", email),
new KeyValuePair<string, string>("Channel", "email")
});
using (var client = new Rest(url))
{
var response = client.PostAsync<TwilioMessageOutput>(url, formContent, authToken).Result;
}
}
}
public class Rest : IDisposable
{
private readonly TimeSpan _timeout;
private HttpClient _httpClient;
private HttpClientHandler _httpClientHandler;
private readonly string _baseUrl;
private const string ClientUserAgent = "twillio-client-v1";
private const string MediaTypeJson = "application/json";
public Rest(string baseUrl, TimeSpan? timeout = null)
{
_baseUrl = NormalizeBaseUrl(baseUrl);
_timeout = timeout ?? TimeSpan.FromSeconds(90);
//_timeout = TimeSpan.FromSeconds(1);
}
private async Task<string> PostAsyncInternal(string url, FormUrlEncodedContent input, string authToken)
{
try
{
EnsureHttpClientCreated();
var byteArray = Encoding.ASCII.GetBytes(authToken);
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
using (var response = await _httpClient.PostAsync(url, input))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
catch (Exception ex)
{
throw ex;
}
}
public async Task<TResult> PostAsync<TResult>(string url, FormUrlEncodedContent input, string authToken) where TResult : class, new()
{
var strResponse = await PostAsyncInternal(url, input, authToken);
return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
public void Dispose()
{
_httpClientHandler?.Dispose();
_httpClient?.Dispose();
}
private void CreateHttpClient()
{
_httpClientHandler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
};
_httpClient = new HttpClient(_httpClientHandler, false)
{
Timeout = _timeout
};
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ClientUserAgent);
if (!string.IsNullOrWhiteSpace(_baseUrl))
{
_httpClient.BaseAddress = new Uri(_baseUrl);
}
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeJson));
}
private void EnsureHttpClientCreated()
{
if (_httpClient == null)
{
CreateHttpClient();
}
}
private static string ConvertToJsonString(object obj)
{
if (obj == null)
{
return string.Empty;
}
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
private static string NormalizeBaseUrl(string url)
{
return url.EndsWith("/") ? url : url + "/";
}
}
Twilio developer evangelist here.
I'm not a C# or Dynamics developer, so sorry if this doesn't help. When you make the request:
var response = client.PostAsync<TwilioMessageOutput>(url, formContent, authToken).Result;
it is an asynchronous request, but you do not seem to be waiting for the asynchronous response at all. Should that be?
var response = await client.PostAsync<TwilioMessageOutput>(url, formContent, authToken).Result;
I've implemented Identity Server and it's working also.
One of my client is an MVC client and during authentication, I want to show the consent screen. For this on the client config I added 'RequireConsent=true'
Now it shows consent screen but the issue is, There it shows only permissions for 'openid' and 'profile' scopes.
I have several other custom scopes like 'Api1.read', 'Api1.write' which are not fetching on Authorization request while Identity Server builds view modal for concent screen.
What I'm doing wrong.
On the client AllowedScopes contains =
{ 'openid', 'profile', 'Api1.read', 'Api1.write' }
When it hits the consent page ApiResources and ApiScopes are empty but openid and profile are available inside IdentityResources
This is how I'm configuring IdentityServer on Startup
services.AddIdentityServer(options =>
{
options.Authentication.CookieLifetime = TimeSpan.FromSeconds(config.IdentityServerCookieLifetime);
})
.AddDeveloperSigningCredential()
.AddCorsPolicyService<MyCORSPolicy>()
.AddResourceStore<MyResourceStore>()
.AddClientStore<MyClientStore>()
.AddProfileService<ProfileService>()
.AddDeveloperSigningCredential();
I'm using IClientStore and IResourceStore to implement fetching details from a database instead of static configuration in appsettings.json
I also don't want to use Entity Framework core for this. I prefer using my own custom table schemas and Dapper.
Here's the Startup config on MVC application
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
//Add Support for OAuth 2.0 Code-Grant With Identity Server 4
services.AddAuthentication(opt =>
{
opt.DefaultScheme = "Cookies";
opt.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", opt =>
{
opt.SignInScheme = "Cookies";
opt.Authority = "https://localhost:5005";
opt.ClientId = "mvc-client";
opt.ResponseType = "code";
opt.ClientSecret = "MVCSecret";
opt.UseTokenLifetime = true;
opt.SaveTokens = true;
});
}
This is my ResourceStore Implementation
public class MyResourceStore : IResourceStore
{
private readonly IConfiguration config;
private readonly string connectionString;
public MyResourceStore(IConfiguration config)
{
this.config = config;
this.connectionString = config.GetConnectionString("AuthConfigDatabase");
}
public async Task<IEnumerable<IdentityServer4.Models.ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames)
{
var apis = SqlHelper.Query<AuthApiResources>($"SELECT * FROM AuthApiResources WHERE Name='{apiResourceNames}' AND IsActive=1", connectionString);
if (apis != null)
{
var result = new List<IdentityServer4.Models.ApiResource>();
foreach (var api in apis)
{
var availableScopes = new List<string>() { "openid", "profile" };
availableScopes.AddRange(api.SupportedScopes.Split(",").ToList());
result.Add(new IdentityServer4.Models.ApiResource
{
Name = api.Name,
DisplayName = api.DisplayName,
Scopes = availableScopes
});
}
return result;
}
return null;
}
public async Task<IEnumerable<IdentityServer4.Models.ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopesList)
{
var scopeNames = scopesList.ToList();
var likeStatements = "";
for (var i = 0; i < scopeNames.Count(); i++)
{
if (i == scopeNames.Count() - 1)
{
likeStatements += $"SupportedScopes LIKE '%{scopeNames[i]}%'";
}
else
{
likeStatements += $"SupportedScopes LIKE '%{scopeNames[i]}%' OR ";
}
}
var apis = SqlHelper.Query<AuthApiResources>($"SELECT * FROM AuthApiResources WHERE ({likeStatements}) AND IsActive=1", connectionString);
if (apis != null)
{
var result = new List<IdentityServer4.Models.ApiResource>();
foreach (var api in apis)
{
var availableScopes = new List<string>() { "openid", "profile" };
availableScopes.AddRange(api.SupportedScopes.Split(",").ToList());
result.Add(new IdentityServer4.Models.ApiResource
{
Name = api.Name,
DisplayName = api.DisplayName,
Scopes = availableScopes
});
}
return result;
}
return null;
}
public async Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopesList)
{
var scopeNames = scopesList.ToList();
var likeStatements = "";
for (var i = 0; i < scopeNames.Count(); i++)
{
if (i == scopeNames.Count() - 1)
{
likeStatements += $"ScopeName='{scopeNames[i]}'";
}
else
{
likeStatements += $"ScopeName='{scopeNames[i]}' OR ";
}
}
var scopes = SqlHelper.Query<AuthScope>($"SELECT * FROM AuthScopes WHERE ({likeStatements})", connectionString);
if (scopes != null)
{
var result = new List<IdentityServer4.Models.ApiScope>();
foreach (var scope in scopes)
{
result.Add(new IdentityServer4.Models.ApiScope
{
Name = scope.ScopeName,
DisplayName = scope.ScopeDescription
});
}
return result;
}
return null;
}
public async Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames)
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
public async Task<Resources> GetAllResourcesAsync()
{
var allResources = new Resources();
allResources.IdentityResources =
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
var apis = SqlHelper.Query<AuthApiResources>($"SELECT * FROM AuthApiResources WHERE IsActive=1", connectionString);
if (apis != null)
{
var result = new List<IdentityServer4.Models.ApiResource>();
foreach (var api in apis)
{
var availableScopes = new List<string>() { "openid", "profile" };
availableScopes.AddRange(api.SupportedScopes.Split(",").ToList());
result.Add(new IdentityServer4.Models.ApiResource
{
Name = api.Name,
DisplayName = api.DisplayName,
Scopes = availableScopes
});
}
allResources.ApiResources = result;
}
var scopes = SqlHelper.Query<AuthScope>($"SELECT * FROM AuthScopes", connectionString);
if (scopes != null)
{
var result = new List<IdentityServer4.Models.ApiScope>();
foreach (var scope in scopes)
{
result.Add(new IdentityServer4.Models.ApiScope
{
Name = scope.ScopeName,
DisplayName = scope.ScopeDescription
});
}
allResources.ApiScopes = result;
}
return allResources;
}
}
And here's a sample of database schema
What am I doing wrong
In your client, inside the AddOpenIdConnect method, you need to also define what scopes you want to have access to, like:
.AddOpenIdConnect(options =>
{
...
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("employee_info");
...
}
I apologize in advance for the bad editing skills, I'm new at posting questions
I'm trying to call my c# api from an angular app, however the request is stuck on pending. I don't think theres a mistake in my typescript code but I'm new to c# so it might be something to do with the api, possibly because of the asyncs.
Here's the api code
{
private static readonly string Tenant = "https://login.microsoftonline.com/{azure.tenant}/";
private static readonly string Resource = "https://analysis.windows.net/powerbi/api";
private string getToken()
{
var authContext = new AuthenticationContext(Tenant);
var clientCredential = new ClientCredential(AppId, AppSecret);
var token = authContext.AcquireTokenAsync(Resource, clientCredential);
return token.Result.AccessToken;
}
private PowerBIClient getPBIClient()
{
var token = new TokenCredentials(getToken(), "Bearer");
return new PowerBIClient(new Uri("https://api.powerbi.com/"), token);
}
public async Task<ODataResponseListReport> getReportsAsync(string ws)
{
PowerBIClient pbiClient = getPBIClient();
var reports = await pbiClient.Reports.GetReportsInGroupAsync(ws);
return reports;
}
public async Task<ReportEmbeddingData> getReportEmbeddingDataAsync(string ws, string rep)
{
PowerBIClient pbiClient = getPBIClient();
var report = await pbiClient.Reports.GetReportInGroupAsync(ws, rep);
var embedUrl = report.EmbedUrl;
var reportName = report.Name;
GenerateTokenRequest genTokenReqParam = new GenerateTokenRequest(accessLevel: "view");
string embedToken = (await pbiClient.Reports.GenerateTokenInGroupAsync(ws, rep, genTokenReqParam)).Token;
return new ReportEmbeddingData {
reportId = rep,
reportName = reportName,
embedUrl = embedUrl,
accessToken = embedToken
};
}
}
And here is my angular call that's being done on ngOnInit
getReports(){
this.http.get("http://localhost:44391/api/Auth?ws="+this.workspaceId)
.subscribe((response) => {
this.reportArray = response;
console.log(this.reportArray);
});
}
Currently in my Xamairn.Android I have 2 activities Mainactivity and SignoutActivity.
In my Mainactivity I have a button where when user clicks the button it goes to SignoutActivity.
Here I have implemented the signout codes of Auth0.
Here are the codes-
In MainAcitivity-
this.Signout.Click += delegate
{
using (var intent = new Intent(this, typeof(SignOutActivity)))
{
this.StartActivity(intent);
}
};
SignOutAcitivity-
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
this.LogoutAsync();
Finish();
}
public async Task<BrowserResult> LogoutAsync()
{
var clientOptions = new Auth0.OidcClient.Auth0ClientOptions
{
Domain ="",
ClientId = "",
Scope = "openid email offline_access",
Browser = new PlatformWebView()
};
var logoutUrl = "Signout URl";
string redirectUri = "I have proper callback uri";
Dictionary<string, string> dictionary = new Dictionary<string, string>();
dictionary.Add("client_id", "random1234");
dictionary.Add("returnTo", clientOptions.RedirectUri);
string endSessionUrl = new RequestUrl(logoutUrl).Create(dictionary);
var logoutRequest = new LogoutRequest();
BrowserResult browserResult = null;
browserResult = await clientOptions.Browser.InvokeAsync(new BrowserOptions(endSessionUrl, redirectUri)
{
Timeout = TimeSpan.FromSeconds((double)logoutRequest.BrowserTimeout),
DisplayMode = logoutRequest.BrowserDisplayMode
});
return browserResult;
}
Platformview-
class PlatformWebView : IBrowser
{
public Task<BrowserResult> InvokeAsync(BrowserOptions options)
{
if (string.IsNullOrWhiteSpace(options.StartUrl))
{
throw new ArgumentException("Missing StartUrl", nameof(options));
}
if (string.IsNullOrWhiteSpace(options.EndUrl))
{
throw new ArgumentException("Missing EndUrl", nameof(options));
}
var tcs = new TaskCompletionSource<BrowserResult>();
void Callback(string response)
{
ActivityMediator.Instance.ActivityMessageReceived -= Callback;
// set result
if (response == "UserCancel")
{
tcs.SetResult(new BrowserResult { ResultType = BrowserResultType.UserCancel });
}
else
{
tcs.SetResult(new BrowserResult
{
Response = response,
ResultType = BrowserResultType.Success
});
}
}
ActivityMediator.Instance.ActivityMessageReceived += Callback;
var uri = Android.Net.Uri.Parse(options.StartUrl);
var intent = new Intent(Intent.ActionView, uri);
intent.AddFlags(ActivityFlags.NoHistory)
.AddFlags(ActivityFlags.NewTask);
Application.Context.StartActivity(intent);
return tcs.Task;
}
}
}
Problem is browser never returns to app,Also I have put a debugger at return browserResult,this debugger also does not hit.Please help me.
I'm trying to do a patch http request to change one of the fields in TFS through the TFS REST API. I've tried several approaches, but I always end up with 400 error. Here is what I have right now:
public void SetFieldValue(string value, string path, int id)
{
var httpWebRequest = (HttpWebRequest)WebRequest.Create(PatchwebAPIUrl("wit/workitems", id.ToString()));
httpWebRequest.ContentType = "application/json-patch+json";
httpWebRequest.Method = "PATCH";
httpWebRequest.Headers["Authorization"] = "Basic" + Base64authorizationToken();
using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
string json = "[{\"op\":\"replace\"," +
$"\"path\":\"{path}\"," +
$"\"value\":\"{value}\"}}]";
streamWriter.Write(JsonConvert.SerializeObject(json));
streamWriter.Flush();
streamWriter.Close();
}
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
}
}
And the test method that calls this method:
[TestMethod()]
public void setFieldValue()
{
TFSWebAPIImplementation webAPI = new TFSWebAPIImplementation();
webAPI.SetFieldValue("654321", "/fields/Custom.Tracking", 61949);
}
The PatchwebAPIUrl("...") Method is fine, and returns a good URL, when I navigate to it I get the JSON data that I want to edit. I'm not 100% on the path variable but it's used the same as the example provided from Microsoft. The authorization works, just based on the fact that when I mess with it I get a 401 instead.
This is my sample code:
Class for work item:
public class WorkItemAtrr
{
[JsonProperty("id")]
public int id;
[JsonProperty("rev")]
public int rev;
[JsonProperty("fields")]
public Dictionary<string, string> fields;
[JsonProperty("_links")]
public Dictionary<string, Link> _links;
[JsonProperty("relations")]
public List<Relation> relations;
[JsonProperty("url")]
public string url;
}
public class Link
{
[JsonProperty("href")]
public string href;
}
public class Relation
{
[JsonProperty("rel")]
public string rel;
[JsonProperty("url")]
public string url;
[JsonProperty("attributes")]
public RelationAttribute attributes;
}
public class RelationAttribute
{
[JsonProperty("comment")]
public string comment = "";
[JsonProperty("isLocked")]
public bool isLocked;
}
Class for new and updated fields:
public class NewField
{
[JsonProperty("op")]
public string op = "add";
[JsonProperty("path")]
public string path;
[JsonProperty("value")]
public object value;
}
Class for exceptions:
public class RestApiExceptionContainer
{
[JsonProperty("id")]
public int id;
[JsonProperty("innerException")]
public string innerException;
[JsonProperty("message")]
public string message;
[JsonProperty("typeName")]
public string typeName;
[JsonProperty("typeKey")]
public string typeKey;
[JsonProperty("errorCode")]
public int errorCode;
[JsonProperty("evenId")]
public int eventId;
}
Method for update a work item:
private static WorkItemAtrr UpdateWorkItemRest()
{
Dictionary<string, string> _fields = new Dictionary<string, string>();
_fields.Add("REFERENCE_NAME", "VALUE");
var _updatedWi = UpdateWorkItem("ID", _fields).Result;
}
Method for preparing request:
public async Task<WorkItemAtrr> UpdatedWorkItem(int pId, Dictionary<String, String> pFields)
{
//PATCH https://{instance}/DefaultCollection/_apis/wit/workitems/{id}?api-version={version}
string _query_url = String.Format("https://YOUR_SERVER/DefaultCollection/_apis/wit/workitems/{id}?api-version=1.0", pId);
List<Object> flds = new List<Object>();
foreach (var _key in pFields.Keys)
flds.Add(new NewField { op = "add", path = "/fields/" + _key, value = pFields[_key] });
HttpResponseMessage _response = await DoRequest(_query_url, JsonConvert.SerializeObject(flds), ClientMethod.PATCH);
return JsonConvert.DeserializeObject<WorkItemAtrr>(await ProcessResponse(_response));
}
Universal method for request:
private async Task<HttpResponseMessage> DoRequest(string pRequest, string pBody, ClientMethod pClientMethod)
{
try
{
HttpClientHandler _httpclienthndlr = new HttpClientHandler();
//update for your auth
if (UseDefaultCredentials) _httpclienthndlr.Credentials = CredentialCache.DefaultCredentials;
else if (TFSDomain == "") _httpclienthndlr.Credentials = new NetworkCredential(TFSUserName, TFSPassword);
else _httpclienthndlr.Credentials = new NetworkCredential(TFSUserName, TFSPassword, TFSDomain);
using (HttpClient _httpClient = new HttpClient(_httpclienthndlr))
{
switch (pClientMethod)
{
case ClientMethod.GET:
return await _httpClient.GetAsync(pRequest);
case ClientMethod.POST:
return await _httpClient.PostAsync(pRequest, new StringContent(pBody, Encoding.UTF8, "application/json"));
case ClientMethod.PATCH:
var _request = new HttpRequestMessage(new HttpMethod("PATCH"), pRequest);
_request.Content = new StringContent(pBody, Encoding.UTF8, "application/json-patch+json");
return await _httpClient.SendAsync(_request);
default:
return null;
}
}
}
catch (Exception _ex)
{
throw new Exception("Http Request Error", _ex);
}
}
Universal method for response:
public async Task<string> ProcessResponse(HttpResponseMessage pResponse)
{
string _responseStr = "";
if (pResponse != null)
{
if (pResponse.IsSuccessStatusCode)
_responseStr = await pResponse.Content.ReadAsStringAsync();
else
{
_responseStr = await pResponse.Content.ReadAsStringAsync();
var _error = JsonConvert.DeserializeObject<RestApiExceptionContainer>(_responseStr);
throw new RestApiException(_error);
}
}
return _responseStr;
}
A 400 means that the request was malformed. In other words, the data stream sent by the client to the server didn't follow the rules.
In the case of a REST API with a JSON payload, 400's are typically, used to indicate that the JSON is invalid in some way according to the API specification for the service.
So, the issue is caused by the JSON body.
Just try it like below:
string json = "[{\"op\":\"replace\",\"path\":\"/fields/System.Title\",\"value\":\"Title\"}]";
You can also use below sample to update the fields with PATCH method via the REST API, it works for me:
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;
namespace UpdateWorkItemFiled0411
{
class Program
{
static void Main(string[] args)
{
string password = "xxxx";
string credentials = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "username", password )));
Object[] patchDocument = new Object[1];
patchDocument[0] = new { op = "replace", path = "/relations/attributes/comment", value = "Adding traceability to dependencies" };
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);
var patchValue = new StringContent(JsonConvert.SerializeObject(patchDocument), Encoding.UTF8, "application/json-patch+json");
var method = new HttpMethod("PATCH");
var request = new HttpRequestMessage(method, "http://server:8080/tfs/DefaultCollection/_apis/wit/workitems/21?api-version=1.0") { Content = patchValue };
var response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().Result;
}
}
}
}
}
Also you may use nugate package Microsoft.TeamFoundationServer.Client.
Connect to team project from here:
using Microsoft.TeamFoundation.Core.WebApi;
using Microsoft.VisualStudio.Services.Common;
...
//create uri and VssBasicCredential variables
Uri uri = new Uri(url);
VssBasicCredential credentials = new VssBasicCredential("", personalAccessToken);
using (ProjectHttpClient projectHttpClient = new ProjectHttpClient(uri, credentials))
{
IEnumerable<TeamProjectReference> projects = projectHttpClient.GetProjects().Result;
}
my code to add comments:
JsonPatchDocument PatchDocument = new JsonPatchDocument();
PatchDocument.Add(
new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/fields/System.History",
Value = "Changes from script"
}
);
VssCredentials Cred = new VssCredentials(true);
WorkItemTrackingHttpClient WIClient = new WorkItemTrackingHttpClient(new Uri("http://YOUR_SERVER/tfs/DefaultCollection"), Cred);
WorkItem result = WIClient.UpdateWorkItemAsync(PatchDocument, id).Result;
Okay guys, so unfortunately none of your solutions worked, and I think it's because there was always an extra set of curly brackets on the outside that the TFS API didn't like. Here is my solution that solved my problem:
public void SetFieldValue(string value, string path, int id)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Base64authorizationToken());
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
using (JsonWriter writer = new JsonTextWriter(sw))
{
writer.Formatting = Formatting.Indented;
writer.WriteStartArray(); // [
writer.WriteStartObject(); // {
writer.WritePropertyName("op"); // "Product:"
writer.WriteValue("replace");
writer.WritePropertyName("path");
writer.WriteValue(path);
writer.WritePropertyName("value");
writer.WriteValue(value);
writer.WriteEndObject(); //}
writer.WriteEnd(); // ]
}
var method = new HttpMethod("PATCH");
var request = new HttpRequestMessage(method, PatchwebAPIUrl("wit/workitems", id.ToString())) { Content = new StringContent(sb.ToString().Trim(new char[] {'{','}'}), Encoding.UTF8, "application/json-patch+json") };
var response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().Result;
}
}
}