How to unit test Soap service access from Crm 2011 Silverlight application? - silverlight

In a Silverlight 5 application in Dynamics CRM 2011 I access the Organization Service of the CRM to query for entity Metadata. I wrote a service that takes an entity name and returns a list of all its fields.
How can I test this service method automatically? The main problem is how to obtain a reference to the organization service from a Silverlight app that does not run in the context of the CRM.
My Service method looks like this:
public IOrganizationService OrganizationService
{
get
{
if (_organizationService == null)
_organizationService = SilverlightUtility.GetSoapService();
return _organizationService;
}
set { _organizationService = value; }
}
public async Task<List<string>> GetAttributeNamesOfEntity(string entityName)
{
// build request
OrganizationRequest request = new OrganizationRequest
{
RequestName = "RetrieveEntity",
Parameters = new ParameterCollection
{
new XrmSoap.KeyValuePair<string, object>()
{
Key = "EntityFilters",
Value = EntityFilters.Attributes
},
new XrmSoap.KeyValuePair<string, object>()
{
Key = "RetrieveAsIfPublished",
Value = true
},
new XrmSoap.KeyValuePair<string, object>()
{
Key = "LogicalName",
Value = "avobase_tradeorder"
},
new XrmSoap.KeyValuePair<string, object>()
{
Key = "MetadataId",
Value = new Guid("00000000-0000-0000-0000-000000000000")
}
}
};
// fire request
IAsyncResult result = OrganizationService.BeginExecute(request, null, OrganizationService);
// wait for response
TaskFactory<OrganizationResponse> tf = new TaskFactory<OrganizationResponse>();
OrganizationResponse response = await tf.FromAsync(
OrganizationService.BeginExecute(request, null, null), iar => OrganizationService.EndExecute(result));
// parse response
EntityMetadata entities = (EntityMetadata)response["EntityMetadata"];
return entities.Attributes.Select(attr => attr.LogicalName).ToList();
}
Edit:
I can create and execute unit tests with Resharper and AgUnit. Thus, the problem is not how to write a unit test in general.

I have tweaked the GetSoapService from the standard Microsoft SDK to accept a fall back value. This means no codes changes are needed when debugging in visual studio and running in CRM. Anyway here it is
public static IOrganizationService GetSoapService(string FallbackValue = null)
{
Uri serviceUrl = new Uri(GetServerBaseUrl(FallbackValue)+ "/XRMServices/2011/Organization.svc/web");
BasicHttpBinding binding = new BasicHttpBinding(Uri.UriSchemeHttps == serviceUrl.Scheme
? BasicHttpSecurityMode.Transport : BasicHttpSecurityMode.TransportCredentialOnly);
binding.MaxReceivedMessageSize = int.MaxValue;
binding.MaxBufferSize = int.MaxValue;
binding.SendTimeout = TimeSpan.FromMinutes(20);
IOrganizationService ser =new OrganizationServiceClient(binding, new EndpointAddress(serviceUrl));
return ser;
}
public static string GetServerBaseUrl(string FallbackValue = null)
{
try
{
string serverUrl = (string)GetContext().Invoke("getClientUrl");
//Remove the trailing forwards slash returned by CRM Online
//So that it is always consistent with CRM On Premises
if (serverUrl.EndsWith("/"))
{
serverUrl = serverUrl.Substring(0, serverUrl.Length - 1);
}
return serverUrl;
}
catch
{
//Try the old getServerUrl
try
{
string serverUrl = (string)GetContext().Invoke("getServerUrl");
//Remove the trailing forwards slash returned by CRM Online
//So that it is always consistent with CRM On Premises
if (serverUrl.EndsWith("/"))
{
serverUrl = serverUrl.Substring(0, serverUrl.Length - 1);
}
return serverUrl;
}
catch
{
return FallbackValue;
}
}

Related

Tenant to tenant user migration in Azure Active Directory using Graph API

Is it possible to migrate users using the MS Graph API in Azure AD?
If so, please explain how to migrate users from one tenant to the other using the MS Graph API.
You can export the users with MS Graph. Note, you can't export the passwords. This means that you have to create a new password and share it with the users. Or choose a random password and let the users reset their password using the self-service password rest feature.
Here is an example how to export the users from a directly
public static async Task ListUsers(GraphServiceClient graphClient)
{
Console.WriteLine("Getting list of users...");
DateTime startTime = DateTime.Now;
Dictionary<string, string> usersCollection = new Dictionary<string, string>();
int page = 0;
try
{
// Get all users
var users = await graphClient.Users
.Request()
.Select(e => new
{
e.DisplayName,
e.Id
}).OrderBy("DisplayName")
.GetAsync();
// Iterate over all the users in the directory
var pageIterator = PageIterator<User>
.CreatePageIterator(
graphClient,
users,
// Callback executed for each user in the collection
(user) =>
{
usersCollection.Add(user.DisplayName, user.Id);
return true;
},
// Used to configure subsequent page requests
(req) =>
{
var d = DateTime.Now - startTime;
Console.WriteLine($"{string.Format(TIME_FORMAT, d.Days, d.Hours, d.Minutes, d.Seconds)} users: {usersCollection.Count}");
// Set a variable to the Documents path.
string filePrefix = "0";
if (usersCollection.Count >= 1000000)
{
filePrefix = usersCollection.Count.ToString()[0].ToString();
}
page++;
if (page >= 50)
{
page = 0;
string docPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), $"users_{filePrefix}.json");
System.IO.File.WriteAllTextAsync(docPath, JsonSerializer.Serialize(usersCollection));
}
Thread.Sleep(200);
return req;
}
);
await pageIterator.IterateAsync();
// Write last page
string docPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), $"users_all.json");
System.IO.File.WriteAllTextAsync(docPath, JsonSerializer.Serialize(usersCollection));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
After you export the users, you can import them back to the other tenant. The following example creates test users. Change the code to set the values from the files you exported earlier. Also, this code uses batch with 20 users in single operation.
public static async Task CreateTestUsers(GraphServiceClient graphClient, AppSettings appSettings, bool addMissingUsers)
{
Console.Write("Enter the from value: ");
int from = int.Parse(Console.ReadLine()!);
Console.Write("Enter the to value: ");
int to = int.Parse(Console.ReadLine()!);
int count = 0;
Console.WriteLine("Starting create test users operation...");
DateTime startTime = DateTime.Now;
Dictionary<string, string> existingUsers = new Dictionary<string, string>();
// Add the missing users
if (addMissingUsers)
{
// Set a variable to the Documents path.
string docPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "users.json");
if (!System.IO.File.Exists(docPath))
{
Console.WriteLine("Can't find the '{docPath}' file.");
}
string usersFile = System.IO.File.ReadAllText(docPath);
existingUsers = JsonSerializer.Deserialize<Dictionary<string, string>>(usersFile);
if (existingUsers == null)
{
Console.WriteLine("Can't deserialize users");
return;
}
Console.WriteLine($"There are {existingUsers.Count} in the directory");
}
List<User> users = new List<User>();
// The batch object
var batchRequestContent = new BatchRequestContent();
for (int i = from; i < to; i++)
{
// 1,000,000
string ID = TEST_USER_PREFIX + i.ToString().PadLeft(7, '0');
if (addMissingUsers)
{
if (existingUsers.ContainsKey(ID))
continue;
}
count++;
try
{
var user = new User
{
DisplayName = ID,
JobTitle = ID.Substring(ID.Length - 1),
Identities = new List<ObjectIdentity>()
{
new ObjectIdentity
{
SignInType = "userName",
Issuer = appSettings.TenantName,
IssuerAssignedId = ID
},
new ObjectIdentity
{
SignInType = "emailAddress",
Issuer = appSettings.TenantName,
IssuerAssignedId = $"{ID}#{TEST_USER_SUFFIX}"
}
},
PasswordProfile = new PasswordProfile
{
Password = "1",
ForceChangePasswordNextSignIn = false
},
PasswordPolicies = "DisablePasswordExpiration,DisableStrongPassword"
};
users.Add(user);
if (addMissingUsers)
{
Console.WriteLine($"Adding missing {ID} user");
}
// POST requests are handled a bit differently
// The SDK request builders generate GET requests, so
// you must get the HttpRequestMessage and convert to a POST
var jsonEvent = graphClient.HttpProvider.Serializer.SerializeAsJsonContent(user);
HttpRequestMessage addUserRequest = graphClient.Users.Request().GetHttpRequestMessage();
addUserRequest.Method = HttpMethod.Post;
addUserRequest.Content = jsonEvent;
if (batchRequestContent.BatchRequestSteps.Count >= BATCH_SIZE)
{
var d = DateTime.Now - startTime;
Console.WriteLine($"{string.Format(TIME_FORMAT, d.Days, d.Hours, d.Minutes, d.Seconds)}, count: {count}, user: {ID}");
// Run sent the batch requests
var returnedResponse = await graphClient.Batch.Request().PostAsync(batchRequestContent);
// Dispose the HTTP request and empty the batch collection
foreach (var step in batchRequestContent.BatchRequestSteps) ((BatchRequestStep)step.Value).Request.Dispose();
batchRequestContent = new BatchRequestContent();
}
// Add the event to the batch operations
batchRequestContent.AddBatchRequestStep(addUserRequest);
// Console.WriteLine($"User '{user.DisplayName}' successfully created.");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}

Session Id (sid) is not assigned during automatic login via IdentityServer4, what gives?

Questions
First question, what determines if an sid claim is emitted from identityserver?
Second question, do I even need an sid? I currently have it included because it was in the sample..
Backstory
I have one website that uses IdentityServer4 for authentication and one website that doesn't. I've cobbled together a solution that allows a user to log into the non-identityserver4 site and click a link that uses one-time-access codes to automatically log into the identityserver4 site. Everything appears to work except the sid claim isn't passed along from identityserver to the site secured by identityserver when transiting from the non-identityserver site. If I log directly into the identityserver4 secured site the sid is included in the claims. Code is adapted from examples of automatically logging in after registration and/or impersonation work flows.
Here is the code:
One time code login process in identityserver4
public class CustomAuthorizeInteractionResponseGenerator : AuthorizeInteractionResponseGenerator
{
...
//https://stackoverflow.com/a/51466043/391994
public override async Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request,
ConsentResponse consent = null)
{
string oneTimeAccessToken = request.GetAcrValues().FirstOrDefault(x => x.Split(':')[0] == "otac");
string clientId = request.ClientId;
//handle auto login handoff
if (!string.IsNullOrWhiteSpace(oneTimeAccessToken))
{
//https://benfoster.io/blog/identity-server-post-registration-sign-in/
oneTimeAccessToken = oneTimeAccessToken.Split(':')[1];
OneTimeCodeContract details = await GetOTACFromDatabase(oneTimeAccessToken);
if (details.IsValid)
{
UserFormContract user = await GetPersonUserFromDatabase(details.PersonId);
if (user != null)
{
string subjectId = await GetClientSubjectIdAsync(clientId, user.AdUsername);
var iduser = new IdentityServerUser(subjectId)
{
DisplayName = user.AdUsername,
AuthenticationTime = DateTime.Now,
IdentityProvider = "local",
};
request.Subject = iduser.CreatePrincipal();
//revoke token
bool? success = await InvalidateTokenInDatabase(oneTimeAccessToken);
if (success.HasValue && !success.Value)
{
Log.Debug($"Revoke failed for {oneTimeAccessToken} it should expire at {details.ExpirationDate}");
}
//https://stackoverflow.com/a/56237859/391994
//sign them in
await _httpContextAccessor.HttpContext.SignInAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme, request.Subject, null);
return new InteractionResponse
{
IsLogin = false,
IsConsent = false,
};
}
}
}
return await base.ProcessInteractionAsync(request, consent);
}
}
Normal Login flow when logging directly into identityserver4 secured site (from sample)
public class AccountController : Controller
{
/// <summary>
/// Handle postback from username/password login
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
Log.Information($"login request from: {Request.HttpContext.Connection.RemoteIpAddress.ToString()}");
if (ModelState.IsValid)
{
// validate username/password against in-memory store
if (await _userRepository.ValidateCredentialsAsync(model.Username, model.Password))
{
AuthenticationProperties props = null;
// only set explicit expiration here if persistent.
// otherwise we reply upon expiration configured in cookie middleware.
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
var clientId = await _account.GetClientIdAsync(model.ReturnUrl);
// issue authentication cookie with subject ID and username
var user = await _userRepository.FindByUsernameAsync(model.Username, clientId);
var iduser = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.UserName
};
await HttpContext.SignInAsync(iduser, props);
// make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint
if (_interaction.IsValidReturnUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
return Redirect("~/");
}
ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage);
}
// something went wrong, show form with error
var vm = await _account.BuildLoginViewModelAsync(model);
return View(vm);
}
}
AuthorizationCodeReceived in identityserver4 secured site
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
tokenEndpoint,
electionClientId,
electionClientSecret);
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// use the access token to retrieve claims from userinfo
var userInfoClient = new UserInfoClient(
new Uri(userInfoEndpoint).ToString());
var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
Claim subject = userInfoResponse.Claims.Where(x => x.Type == "sub").FirstOrDefault();
// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(GetRoles(subject.Value, tokenClient, apiResourceScope, apiBasePath));
var transformedClaims = StartupHelper.TransformClaims(userInfoResponse.Claims);
id.AddClaims(transformedClaims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
THIS FAILS -> id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
n.AuthenticationTicket.Properties);
},
}
});
}
}
Questions again if you don't want to scroll back up
First question, what determines if an sid claim is emitted from identityserver?
Second question, do I even need an sid? I currently have it included because it was in the sample..

Multiple certificates for Issuer ITfoxtec.Identity.Saml2

As context: I am trying to implement SAML2.0 authentication using ITfoxtec.Identity.Saml2 library. I want to use multiple certificates for one Service Provider, because different clients could login to Service Provider and each of them can have its own certificate. I need a third-party login service have possibility to choose among the list of certificates from my Service Provider metadata.xml when SAML request happened. Does ITfoxtec.Identity.Saml2 library support this possibility or are there some workarounds how it can be implemented?. Thank You
You would normally have one Saml2Configuration. But in your case I would implement some Saml2Configuration logic, where I can ask for a specific Saml2Configuration with the current certificate (SigningCertificate/DecryptionCertificate). This specific Saml2Configuration is then used in the AuthController.
The metadata (MetadataController) would then call the Saml2Configuration logic to get a list of all the certificates.
Something like this:
public class MetadataController : Controller
{
private readonly Saml2Configuration config;
private readonly Saml2ConfigurationLogic saml2ConfigurationLogic;
public MetadataController(IOptions<Saml2Configuration> configAccessor, Saml2ConfigurationLogic saml2ConfigurationLogic)
{
config = configAccessor.Value;
this.saml2ConfigurationLogic = saml2ConfigurationLogic;
}
public IActionResult Index()
{
var defaultSite = new Uri($"{Request.Scheme}://{Request.Host.ToUriComponent()}/");
var entityDescriptor = new EntityDescriptor(config);
entityDescriptor.ValidUntil = 365;
entityDescriptor.SPSsoDescriptor = new SPSsoDescriptor
{
WantAssertionsSigned = true,
SigningCertificates = saml2ConfigurationLogic.GetAllSigningCertificates(),
//EncryptionCertificates = saml2ConfigurationLogic.GetAllEncryptionCertificates(),
SingleLogoutServices = new SingleLogoutService[]
{
new SingleLogoutService { Binding = ProtocolBindings.HttpPost, Location = new Uri(defaultSite, "Auth/SingleLogout"), ResponseLocation = new Uri(defaultSite, "Auth/LoggedOut") }
},
NameIDFormats = new Uri[] { NameIdentifierFormats.X509SubjectName },
AssertionConsumerServices = new AssertionConsumerService[]
{
new AssertionConsumerService { Binding = ProtocolBindings.HttpPost, Location = new Uri(defaultSite, "Auth/AssertionConsumerService") }
},
AttributeConsumingServices = new AttributeConsumingService[]
{
new AttributeConsumingService { ServiceName = new ServiceName("Some SP", "en"), RequestedAttributes = CreateRequestedAttributes() }
},
};
entityDescriptor.ContactPerson = new ContactPerson(ContactTypes.Administrative)
{
Company = "Some Company",
GivenName = "Some Given Name",
SurName = "Some Sur Name",
EmailAddress = "some#some-domain.com",
TelephoneNumber = "11111111",
};
return new Saml2Metadata(entityDescriptor).CreateMetadata().ToActionResult();
}
private IEnumerable<RequestedAttribute> CreateRequestedAttributes()
{
yield return new RequestedAttribute("urn:oid:2.5.4.4");
yield return new RequestedAttribute("urn:oid:2.5.4.3", false);
}
}

google calendar v3 api with OAuth 2.0 service account return empty result

I got zero results from Calendar EventsResource.ListRequest with ServiceAccountCredential. And same code works properly with UserCredential. I don't get any exception or error, just the Events.Items count is always zero when I try to auth with key.p12 file.
I also set up the API client access for https://www.googleapis.com/auth/calendar on the clientID of this service account in domain admin security setting.
Any ideas? Thanks
--------------------------------Here is my code:
static UserCredential getUserCredential()
{
UserCredential credential;
using (var stream = new FileStream(#"client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { CalendarService.Scope.CalendarReadonly },
"user", CancellationToken.None, new FileDataStore("carol test")).Result;
}
return credential;
}
static ServiceAccountCredential getServiceAccountCredential()
{
//Google authentication using OAuth for single calendar
string serviceAccountEmail = "xx...#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"clientPrivateKey.p12", "notasecret",
X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(new
ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes =
new[] { CalendarService.Scope.Calendar }
}.FromCertificate(certificate));
return credential;
}
static void Main(string[] args)
{
// Create the service.
BaseClientService.Initializer initializer = new
BaseClientService.Initializer();
//initializer.HttpClientInitializer = getUserCredential();
initializer.HttpClientInitializer = getServiceAccountCredential();
initializer.ApplicationName = "Google Calendar Sample";
CalendarService calservice = new CalendarService(initializer);
List<CalendarEvent> calendarEvents = new List<CalendarEvent>();
try
{
EventsResource.ListRequest req = calservice.Events.List("primary"); // Is it correct to use "primary" as calendarID?
req.TimeMin = new DateTime(2014, 12, 1);
req.TimeMax = new DateTime(2014, 12, 6);
req.SingleEvents = true;
req.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;
Events events = req.Execute(); // always empty for ServiceAccount auth, but getting results for user credential auth
}
catch (Exception ex)
{
Console.WriteLine(ex.Message); // didnot get exception either way
}
}
Value for events (When use UserCredential)
{Google.Apis.Calendar.v3.Data.Events}
AccessRole: "owner"
DefaultReminders: Count = 1
Description: null
ETag: "\"1417813280083000\""
Items: Count = 2
Kind: "calendar#events"
NextPageToken: null
NextSyncToken: null
Summary: "myemail#mydomain.net"
TimeZone: "America/Los_Angeles"
Updated: {12/5/2014 1:01:20 PM}
UpdatedRaw: "2014-12-05T21:01:20.083Z"
Value for events (when use service account key.p12 auth)
{Google.Apis.Calendar.v3.Data.Events}
AccessRole: "owner"
DefaultReminders: Count = 0
Description: null
ETag: "\"1417747728066000\""
Items: Count = 0
Kind: "calendar#events"
NextPageToken: null
NextSyncToken: null
Summary: "xxxx#developer.gserviceaccount.com"
TimeZone: "UTC"
Updated: {12/4/2014 6:48:48 PM}
UpdatedRaw: "2014-12-05T02:48:48.066Z"

OptionSetValue does not contain constructor which takes one argument

I am creating a silverlight application as a web resource for CRM 2011. Now i am creating a ServiceAppointment record in DB and after creating it i want to change its Status to "reserved" instead of requested.
I googled about this and come across the examples like Close a Service Activity Through Code and Microsoft.Crm.Sdk.Messages.SetStateRequest
They all suggesting to use "SetStateRequest" and for using this i have to set the OptionSetValue like
request["State"] = new OptionSetValue(4);
But above line gives me error saying "OptionSetValue does not contain constructor which takes one argument"
BTW i am using SOAP end point of CRM 2011 service in silverlight application
Any ideas friends?
EDIT
Following is my code
var request = new OrganizationRequest { RequestName = "SetStateRequest" };
request["State"] = 3;
request["Status"] = 4;
request["EntityMoniker"] = new EntityReference() { Id = createdActivityId, LogicalName = "serviceappointment" };
crmService.BeginExecute(request,ChangeActivityStatusCallback,crmService);
And my callback function is
private void ChangeActivityStatusCallback(IAsyncResult result) {
OrganizationResponse response;
try
{
response = ((IOrganizationService)result.AsyncState).EndExecute(result);
}
catch (Exception ex)
{
_syncContext.Send(ShowError, ex);
return;
}
}
You must some how be referencing some other OptionSetValue class that is not the Microsoft.Xrm.Sdk one. Try appending the namespace to see if that resolves your issue:
request["State"] = new Microsoft.Xrm.Sdk.OptionSetValue(4);
Also, why are you using late bound on the SetStateRequest? Just use the SetStateRequest class:
public static Microsoft.Crm.Sdk.Messages.SetStateResponse SetState(this IOrganizationService service,
Entity entity, int state, int? status)
{
var setStateReq = new Microsoft.Crm.Sdk.Messages.SetStateRequest();
setStateReq.EntityMoniker = entity.ToEntityReference();
setStateReq.State = new OptionSetValue(state);
setStateReq.Status = new OptionSetValue(status ?? -1);
return (Microsoft.Crm.Sdk.Messages.SetStateResponse)service.Execute(setStateReq);
}
Thanks Daryl for you time and effort. I have solved my problem with the way u have suggested.
I am posting my code that worked for me.
var request = new OrganizationRequest { RequestName = "SetState" };
request["State"] = new OptionSetValue { Value = 3 };
request["Status"] = new OptionSetValue { Value = 4 };
request["EntityMoniker"] = new EntityReference() { Id = createdActivityId, LogicalName = "serviceappointment" };
crmService.BeginExecute(request,ChangeActivityStatusCallback,crmService);
private void ChangeActivityStatusCallback(IAsyncResult result) {
OrganizationResponse response;
try
{
response = ((IOrganizationService)result.AsyncState).EndExecute(result);
}
catch (Exception ex)
{
_syncContext.Send(ShowError, ex);
return;
}
}

Resources