Copy a note from a task to a case - dynamics-crm

I have a manually invoked process which is tied to the account entity. This process has a number of steps. One of the first steps is to create a task and assign it to someone. It's expected that this person will add some notes and complete the task.
Further down the process, I have a step to create a service case. After this is created, I want to copy the note(s) from the task above to the newly created service case.
I have created a custom workflow activity to try and accomplish this. I have gotten as far as deploying it and using it within my process without any errors and it does copy content into the notes field of the service case, however it copies the title of the task, not the note content and I can't quite fathom out why.
public class CopyNotes : CodeActivity
{
protected override void Execute(CodeActivityContext executionContext)
{
//Create the context
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
//get the notes associated with the source entity
Guid copyFromId = CopyFrom.Get(executionContext).Id;
Guid copyToId = CopyTo.Get(executionContext).Id;
EntityCollection copyFromNotes = RetrieveNotes(service, copyFromId);
if (copyFromNotes.Entities.Any())
{
foreach (Entity e in copyFromNotes.Entities)
{
Entity newNote = new Entity("annotation");
newNote.Attributes["subject"] = e.Attributes["subject"];
newNote.Attributes["notetext"] = e.Attributes["notetext"];
newNote.Attributes["objectid"] = new EntityReference() { Id = copyToId, LogicalName = CopyTo.Get(executionContext).LogicalName };
}
}
}
private EntityCollection RetrieveNotes(IOrganizationService service, Guid relatedObject)
{
ConditionExpression condition = new ConditionExpression();
condition.AttributeName = "objectid";
condition.Operator = ConditionOperator.Equal;
condition.Values.Add(relatedObject.ToString());
ColumnSet columns = new ColumnSet("subject", "notetext");
QueryExpression query = new QueryExpression();
query.ColumnSet = columns;
query.EntityName = "annotation";
query.Criteria.AddCondition(condition);
EntityCollection results = service.RetrieveMultiple(query);
return results;
}
[RequiredArgument]
[ReferenceTarget("task")]
[Input("Copy notes from item")]
public InArgument<EntityReference> CopyFrom { get; set; }
[RequiredArgument]
[ReferenceTarget("incident")]
[Input("Copy notes to item")]
public InArgument<EntityReference> CopyTo { get; set; }
}

I think you need to actually create the newNote after defining it.
foreach (Entity e in copyFromNotes.Entities)
{
Entity newNote = new Entity("annotation");
newNote.Attributes["subject"] = e.Attributes["subject"];
newNote.Attributes["notetext"] = e.Attributes["notetext"];
newNote.Attributes["objectid"] = new EntityReference() { Id = copyToId, LogicalName = CopyTo.Get(executionContext).LogicalName };
service.Create(newNote);
}
Once I did that your code worked just fine creating a new note with both the title and note text.

You could do this with a standard workflow, if Async creation is fast enough.
Andreas

Related

Working on a Dynamics CRM Custom Workflow that updates "Modified By" field -- need help debugging

I am working on a Dynamics CRM CWA that updates the "Modified By" field based on a text field called "Prepared By". I currently have 3 errors that I need some help debugging (see below). They may be pretty easy fixes but I am fairly new to coding. Any help de-bugging would be greatly appreciated. Thanks!
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Threading.Tasks;
using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
using System.Runtime.Serialization;
namespace KED365.Workflows
{
/// </summary>
public class ModifiedBy : WorkFlowActivityBase
{
private Guid contactid;
[Input("User Full Name")]
public InArgument<string> UserFullName { get; set; }
/// <summary>
/// Executes the WorkFlow.
/// </summary>
/// <param name="crmWorkflowContext">The <see cref="LocalWorkflowContext"/> which contains the
/// <param name="executionContext" > <see cref="CodeActivityContext"/>
/// </param>
/// <remarks>
/// For improved performance, Microsoft Dynamics 365 caches WorkFlow instances.
/// The WorkFlow's Execute method should be written to be stateless as the constructor
/// is not called for every invocation of the WorkFlow. Also, multiple system threads
/// could execute the WorkFlow at the same time. All per invocation state information
/// is stored in the context. This means that you should not use global variables in WorkFlows.
/// </remarks>
protected override void Execute(CodeActivityContext activityContext, IWorkflowContext workflowContext, IOrganizationService orgService, ITracingService tracingService)
{
//get entity record for which plugin was fired
Entity _target = (Entity)workflowContext.InputParameters["Target"];
//check if portaluser name is to be obtained from custom createby or from custom modifiedby
if (workflowContext.MessageName.ToUpper() == "CREATE")
{
contactid = _target.Attributes.Contains("new_createdby") ? _target.GetAttributeValue<EntityReference>("new_createdby").Id : Guid.Empty;
}
else
{
contactid = _target.Attributes.Contains("new_modifiedby") ? _target.GetAttributeValue<EntityReference>("new_modifiedby").Id : Guid.Empty;
}
//retrieve contact fullname from contactid
var _contact = activityContext.CreateQuery("contact").Where(c => c.GetAttributeValue<Guid>("contactid").Equals(contactid)).FirstOrDefault();
if (_contact != null)
{
if (_contact.Attributes.Contains("fullname"))
{
fullname = _contact.GetAttributeValue<string>("fullname");
}
//retrieve Systemuser that has same name as that of new_portalcreatedby/ //new_portalmodifiedby
Entity _user = context.CreateQuery("systemuser").Where(e => e.GetAttributeValue<string>("fullname").Equals(fullname)).FirstOrDefault();
if (_user != null)
{
//check if we need to update createdby or modifiedby
if (workflowContext.MessageName.ToUpper() == "CREATE")
{
_target["createdby"] = _user.ToEntityReference();
}
else
{
_target["modifiedby"] = _user.ToEntityReference();
}
//assign new target to plugin executioncontext
workflowContext.InputParameters["Target"] = _target;
}
}
}
}
}
Error 1 :
Severity Code Description Project File Line Suppression State
Error CS1061 'CodeActivityContext' does not contain a definition for 'CreateQuery' and no extension method 'CreateQuery' accepting a first argument of type 'CodeActivityContext' could be found (are you missing a using directive or an assembly reference?) Workflows C:\Users\tgiard\Downloads\GetUserByName-master\GetUserByName-master\Workflows\ModifiedBy.cs 68 Active
Error 2 :
Severity Code Description Project File Line Suppression State
Error CS0103 The name 'fullname' does not exist in the current context Workflows C:\Users\tgiard\Downloads\GetUserByName-master\GetUserByName-master\Workflows\ModifiedBy.cs 75 Active
Error 3 :
Severity Code Description Project File Line Suppression State
Error CS0103 The name 'context' does not exist in the current context Workflows C:\Users\tgiard\Downloads\GetUserByName-master\GetUserByName-master\Workflows\ModifiedBy.cs 79 Active
Here is some feedback on your issues:
Error 1 - 'CodeActivityContext' does not contain a definition for 'CreateQuery'
This issue is related to the lines:
var _contact = activityContext.CreateQuery("contact").Where(c => c.GetAttributeValue<Guid>("contactid").Equals(contactid)).FirstOrDefault();
and
Entity _user = context.CreateQuery("systemuser").Where(e => e.GetAttributeValue<string>("fullname").Equals(fullname)).FirstOrDefault();
I don't know what that method is but you have better options; for the contact you already have the guid, so you can simply use a Retrieve():
var _contact = orgService.Retrieve("contact", contactid, new ColumnSet("fullname"));
And for the system user write a QueryExpression filtering by fullname:
var query = new QueryExpression("systemuser"):
query.Criteria.AddCondition("fullname", ConditionOperator.Equal, fullname);
var _user = orgService.RetrieveMultiple(query).Entities.FirstOrDefault();
Error 2: The name 'fullname' does not exist in the current contex
This is basic C#, you must instantiate your variable before you use it:
string fullname;
Error 3: The name 'context' does not exist in the current context
Ironic and true. This should be activityContext, but we have already fixed this issue in the change we made for Error 1.
Entity _user = context.CreateQuery("systemuser").Where(e => e.GetAttributeValue<string>("fullname").Equals(fullname)).FirstOrDefault();
As Zach Mast correctly pointed out, using a Pre-Operation is recommended. It also seems an odd case where you retrieve a contacts name and match it with a user. Instead, You could change the type of the field to a user reference, add a user field to the contact you retrieve or add a code to match the Contact to the User. This way, you won't have issues with users with the same name or typo's.
Please find below your workflow activity converted to a Pre-Operation plugin.
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Linq;
namespace KED365.Plugins
{
public class CreateUpdateContact : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = factory.CreateOrganizationService(context.UserId);
tracingService.Trace("Start plugin");
tracingService.Trace("Validate Target");
if (!context.InputParameters.Contains("Target") || !(context.InputParameters["Target"] is Entity))
return;
tracingService.Trace("Retrieve Target");
var target = (Entity)context.InputParameters["Target"];
String message = context.MessageName.ToLower();
SetCreatedByAndModifiedBy(tracingService, service, target, message);
}
private void SetCreatedByAndModifiedBy(ITracingService tracingService, IOrganizationService service, Entity target, string message)
{
tracingService.Trace("Start SetPriceList");
tracingService.Trace("Validate Message is Create or Update");
if (!message.Equals("create", StringComparison.OrdinalIgnoreCase) && !message.Equals("update", StringComparison.OrdinalIgnoreCase))
return;
tracingService.Trace("Retrieve Attributes");
var createdByReference = target.GetAttributeValue<EntityReference>("new_createdby");
var modifiedByReference = target.GetAttributeValue<EntityReference>("new_modifiedby");
tracingService.Trace("Retrieve And Set User for Created By");
RetrieveAndSetUser(tracingService, service, target, createdByReference, "createdby");
tracingService.Trace("Retrieve And Set User for Modified By");
RetrieveAndSetUser(tracingService, service, target, modifiedByReference, "modifiedby");
}
private void RetrieveAndSetUser(ITracingService tracingService, IOrganizationService service, Entity target, EntityReference reference, string targetAttribute)
{
tracingService.Trace("Validating Reference");
if (reference == null)
return;
tracingService.Trace("Retrieving and Validating User");
var user = RetrieveUserByName(service, reference.Name, new ColumnSet(false));
if (user == null)
return;
tracingService.Trace("Setting Target Attribute");
target[targetAttribute] = user.ToEntityReference();
}
private Entity RetrieveUserByName(IOrganizationService service, string name, ColumnSet columns)
{
var query = new QueryExpression
{
EntityName = "systemuser",
ColumnSet = columns,
Criteria = new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression
{
AttributeName = "fullname",
Operator = ConditionOperator.Equal,
Values = { name }
}
}
}
};
var retrieveResponse = service.RetrieveMultiple(query);
if (retrieveResponse.Entities.Count == 1)
{
return retrieveResponse.Entities.FirstOrDefault();
}
else
{
// Alternatively you can thrown an error as you have unexpectedly multiple matches
return null;
}
}
}
}

Data binding kendo grid asp.net mvc

Hi guys i need your help !!!!. Is it possible to update data from kendo grid using stored procedure in sql server?
I mean: #Html.Kendo.Grid ->>> Method wich executes stored procedure ; <<<-
All examples that i looked for use "Entity Framework Data Model" for binding data to kendo grid. but i want to use my own class to connect to the database.
public class LessonsDep
{
public int LesId { get; set; }
public int Activated { get; set; }
public string TaskTable { get; set; }
}
public class LessonsBusinessLayer
{
public void changeLessons(LessonsDep lessons){
string connectionString = ConfigurationManager.ConnectionStrings["nisa1415"].ConnectionString;
using (SqlConnection con = new SqlConnection(connectionString))
{
SqlCommand cmd = new SqlCommand("dep.edidBiology",con);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter paramId = new SqlParameter();
paramId.ParameterName = "#LesId";
paramId.Value = LessonNameClass.stcLesId;
cmd.Parameters.Add(paramId);
SqlParameter paramActivated = new SqlParameter();
paramActivated.ParameterName = "#Activated";
paramActivated.Value = lessons.Activated;
cmd.Parameters.Add(paramActivated);
SqlParameter paramTaskTable = new SqlParameter();
paramTaskTable.ParameterName = "#TaskTable";
paramTaskTable.Value = lessons.TaskTable;
cmd.Parameters.Add(paramTaskTable);
con.Open();
cmd.ExecuteNonQuery();
}
}
}
Do you have any problems using what you proposed?
I mean I'm not using Entity in any of my projects ( I use NHibernate) and I think you can use any ORM or other technic instead to read/save/update/delete data.
Of course you have to get data and return it in the same format kendo grid requires.
public virtual JsonResult Read([DataSourceRequest] DataSourceRequest request)
{
//for example use your code code here for SELECT operation to get
//all the elements you want. Assign to List<> list variable and voila
return Json(list.ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
}

Instantiate new System.Web.Http.OData.Query.ODataQueryOptions in nunit test of ASP.NET Web API controller

I have an ASP.NET MVC4 Web API project with an ApiController-inheriting controller that accepts an ODataQueryOptions parameter as one of its inputs.
I am using NUnit and Moq to test the project, which allow me to setup canned responses from the relevant repository methods used by the ApiController. This works, as in:
[TestFixture]
public class ProjectControllerTests
{
[Test]
public async Task GetById()
{
var repo = new Mock<IManagementQuery>();
repo.Setup(a => a.GetProjectById(2)).Returns(Task.FromResult<Project>(new Project()
{
ProjectID = 2, ProjectName = "Test project", ProjectClient = 3
}));
var controller = new ProjectController(repo.Object);
var response = await controller.Get(2);
Assert.AreEqual(response.id, 2);
Assert.AreEqual(response.name, "Test project");
Assert.AreEqual(response.clientId, 3);
}
}
The challenge I have is that, to use this pattern, I need to pass in the relevant querystring parameters to the controller as well as the repository (this was actually my intent). However, in the case of ODataQueryOptions-accepting ApiController methods, even in the cases where I would like to use just the default parameters for ODataQueryOptions, I need to know how to instantiate one. This gets tricky:
ODataQueryOptions does not implement an interface, so I can't mock it directly.
The constructor requires an implementation of System.Web.Http.OData.ODataQueryContext, which requires an implementation of something implementing Microsoft.Data.Edm.IEdmModel, for which the documentation is scarce and Visual Studio 2012 Find References and View Call Hierarchy do not provide insight (what implements that interface?).
What do I need to do/Is there a better way of doing this?
Thanks.
Looks like someone else already answered this in the comments here, but it's not a complete solution for my use-case (see comment below):
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Customer>("Customers");
var opts = new ODataQueryOptions<Customer>(new ODataQueryContext(modelBuilder.GetEdmModel(),typeof(Customer)), request);
This is the solution I have been using in my NUnit tests to inject ODataQueryOptions
private static IEdmModel _model;
private static IEdmModel Model
{
get
{
if (_model == null)
{
var builder = new ODataConventionModelBuilder();
var baseType = typeof(MyDbContext);
var sets = baseType.GetProperties().Where(c => c.PropertyType.IsGenericType && c.PropertyType.GetGenericTypeDefinition() == typeof(IDbSet<>));
var entitySetMethod = builder.GetType().GetMethod("EntitySet");
foreach (var set in sets)
{
var genericMethod = entitySetMethod.MakeGenericMethod(set.PropertyType.GetGenericArguments());
genericMethod.Invoke(builder, new object[] { set.Name });
}
_model = builder.GetEdmModel();
}
return _model;
}
}
public static ODataQueryOptions<T> QueryOptions<T>(string query = null)
{
query = query ?? "";
var url = "http://localhost/Test?" + query;
var request = new HttpRequestMessage(HttpMethod.Get, url);
return new ODataQueryOptions<T>(new ODataQueryContext(Model, typeof(T)), request);
}

LINQ-To-Sharepoint Multiple content types for a single list

I'm using SPMetal in order to generate entity classes for my sharepoint site and I'm not exactly sure what the best practice is to use when there are multiple content types for a single list. For instance I have a task list that contains 2 content types and I'm defining them via the config file for SPMetal. Here is my definition...
<List Member="Tasks" Name="Tasks">
<ContentType Class="LegalReview" Name="LegalReviewContent"/>
<ContentType Class="Approval" Name="ApprovalContent"/>
</List>
This seems to work pretty well in that the generated objects do inherit from WorkflowTask but the generated type for the data context is a List of WorkflowTask. So when I do a query I get back a WorkflowTask object instead of a LegalReview or Approval object. How do I make it return an object of the correct type?
[Microsoft.SharePoint.Linq.ListAttribute(Name="Tasks")]
public Microsoft.SharePoint.Linq.EntityList<WorkflowTask> Tasks {
get {
return this.GetList<WorkflowTask>("Tasks");
}
}
UPDATE
Thanks for getting back to me. I'm not sure how I recreate the type based on the SPListItem and would appreciate any feedback.
ContractManagementDataContext context = new ContractManagementDataContext(_url);
WorkflowTask task = context.Tasks.FirstOrDefault(t => t.Id ==5);
Approval a = new Approval(task.item);
public partial class Approval{
public Approval(SPListItem item){
//Set all properties here for workflowtask and approval type?
//Wouldn't there be issues since it isn't attached to the datacontext?
}
public String SomeProperty{
get{ //get from list item};
set{ //set to list item};
}
Linq2SharePoint will always return an object of the first common base ContentType for all the ContentTypes in the list. This is not only because a base type of some description must be used to combine the different ContentTypes in code but also it will then only map the fields that should definitely exist on all ContentTypes in the list. It is however possible to get access to the underlying SPListItem returned by L2SP and thus from that determine the ContentType and down cast the item.
As part of a custom repository layer that is generated from T4 templates we have a partial addition to the Item class generated by SPMetal which implements ICustomMapping to get the data not usually available on the L2SP entities. A simplified version is below which just gets the ContentType and ModifiedDate to show the methodology; though the full class we use also maps Modified By, Created Date/By, Attachments, Version, Path etc, the principle is the same for all.
public partial class Item : ICustomMapping
{
private SPListItem _SPListItem;
public SPListItem SPListItem
{
get { return _SPListItem; }
set { _SPListItem = value; }
}
public string ContentTypeId { get; internal set; }
public DateTime Modified { get; internal set; }
public virtual void MapFrom(object listItem)
{
SPListItem item = (SPListItem)listItem;
this.SPListItem = item;
this.ContentTypeId = item.ContentTypeId.ToString();
this.Modified = (DateTime)item["Modified"];
}
public virtual void MapTo(object listItem)
{
SPListItem item = (SPListItem)listItem;
item["Modified"] = this.Modified == DateTime.MinValue ? this.Modified = DateTime.Now : this.Modified;
}
public virtual void Resolve(RefreshMode mode, object originalListItem, object databaseObject)
{
SPListItem originalItem = (SPListItem)originalListItem;
SPListItem databaseItem = (SPListItem)databaseObject;
DateTime originalModifiedValue = (DateTime)originalItem["Modified"];
DateTime dbModifiedValue = (DateTime)databaseItem["Modified"];
string originalContentTypeIdValue = originalItem.ContentTypeId.ToString();
string dbContentTypeIdValue = databaseItem.ContentTypeId.ToString();
switch(mode)
{
case RefreshMode.OverwriteCurrentValues:
this.Modified = dbModifiedValue;
this.ContentTypeId = dbContentTypeIdValue;
break;
case RefreshMode.KeepCurrentValues:
databaseItem["Modified"] = this.Modified;
break;
case RefreshMode.KeepChanges:
if (this.Modified != originalModifiedValue)
{
databaseItem["Modified"] = this.Modified;
}
else if (this.Modified == originalModifiedValue && this.Modified != dbModifiedValue)
{
this.Modified = dbModifiedValue;
}
if (this.ContentTypeId != originalContentTypeIdValue)
{
throw new InvalidOperationException("You cannot change the ContentTypeId directly");
}
else if (this.ContentTypeId == originalContentTypeIdValue && this.ContentTypeId != dbContentTypeIdValue)
{
this.ContentTypeId = dbContentTypeIdValue;
}
break;
}
}
}
Once you have the ContentType and the underlying SPListItem available on your L2SP entity it is simply a matter of writing a method which returns an instance of the derived ContentType entity from a combination of the values of the base type and the extra data for the missing fields from the SPListItem.
UPDATE: I don't actually have an example converter class as we don't use the above mapping extension to Item in this way. However I could imagine something like this would work:
public static class EntityConverter
{
public static Approval ToApproval(WorkflowTask wft)
{
Approval a = new Approval();
a.SomePropertyOnWorkflowTask = wft.SomePropertyOnWorkflowTask;
a.SomePropertyOnApproval = wft.SPListItem["field-name"];
return a;
}
}
Or you could put a method on a partial instance of WorkflowTask to return an Approval object.
public partial class WorkflowTask
{
public Approval ToApproval()
{
Approval a = new Approval();
a.SomePropertyOnWorkflowTask = this.SomePropertyOnWorkflowTask;
a.SomePropertyOnApproval = this.SPListItem["field-name"];
return a;
}
public LegalReview ToLegalReview()
{
// Create and return LegalReview as for Approval
}
}
In either situation you would need to determine the method to call to get the derived type from the ContentTypeId property of the WorkflowTask. This is the sort of code I would normally want to generate in one form or another as it will be pretty repetitive but that is a bit off-topic.

model validation unittest using Moq

Hi i am trying to validate model field as required but result is always comming null, I am expecting model will send me error as First name is requied but nothing happening
here is my unittest using Moq
[TestInitialize]
public void Initialize() {
RepositoryMoc= new Mock<ITestRepository>();
_service = new TestService(RepositoryMoc.Object)
}
[TestMethod]
public void UpdateResults_FirstNameRequied() {
User u = new User();
u.FirstName = "";
u.LastName = "Lee";
RepositoryMoc.Setup(r => r.UpdateResults(u));
var result = _service.UpdateResults(u);
Assert.AreEqual("FirstName is required", result.ToString());
}
You've wrote a setup for RepositoryMoc, but it not returning something. If you want that function to return something, write a setup like that:
RepositoryMoc.Setup(r => r.UpdateResults(u)).Returns(<WhateverItHasToReturn>);

Resources