Tuesday, October 9, 2012

Recording Bugs made easy with Windows 7/8 Tools

If you don't use the TFS Testing Framework, bugtracking and recording tools, you can try this simple approach of Scott Hanselman.

Cheers

Tuesday, August 14, 2012

Part 2 - Consuming custom WCF Data Services (oData) Endpoint (Javascript/jQuery and c#) - Create and Delete

In the first post we have created a service which worked fine for retrieving data. most time we also need delete and create functionality. Here is how it works.

This Article is based on the Part 1 - Consuming custom WCF Data Services. If you just want to download the current version or check out the code, check the codeplex project page.

Preparation:
  • Add JSON2 (via NuGet)
  • Add Styles-Folder
Implementation:
  • Configure DataService to V2
At the moment, the V3 Implementation of Microsoft does not support Create Functionality. You will run into a 415 Unsupported Media Type Exception. More information you can find on Stackoverflow.
The solution for this is quite simple. Just change the value for MaxProtocolVersion in your Service from "V3" to "V2" . 



The difficult part of the Create/Delete Functionality was to create a custom IUpdatable Implementation. Well, this is NOT required for your implementation. You can just replace my implementation with your Entity-Framework Context. 
I want my example independent from database/files. So i have created a static collection of items, controlled by the Context.

  • Replace CarContext.cs code with the following.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Data.Services;
 
namespace MK.oDataConsuming.Web.Model {
    public class CarContext : IUpdatable {
 
        #region Members
        private static List<Car> _cars;
        #endregion
 
        #region Constructor
        public CarContext() {
            if (_cars == null) {
                _cars = new List<Car>();
            }
        }
        #endregion
 
        #region Properties
        public IQueryable<Car> Cars {
            get {
                return _cars.AsQueryable<Car>();
            }
        }
        #endregion
 
        #region IUpdatable Methods
        public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded) {
            throw new NotImplementedException();
        }
 
        public void ClearChanges() {
            throw new NotImplementedException();
        }
 
        public object CreateResource(string containerName, string fullTypeName) {
 
            Car car = new Car();
 
            int tmpId = 0;
            if (_cars.Count > 0) {
                tmpId = _cars.Max(c => c.Id);
            }
 
            car.Id = ++tmpId;
            return car;
        }
 
        public void DeleteResource(object targetResource) {
            List<Car> carsToDelete = (List<Car>)targetResource;
            Car carToDelete = carsToDelete[0];
            _cars.Remove(carToDelete);
        }
 
        public object GetResource(IQueryable query, string fullTypeName) {
            List<Car> carList = query.Cast<Car>().ToList();
            return carList;
        }
 
        public object GetValue(object targetResource, string propertyName) {
            throw new NotImplementedException();
        }
 
        public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved) {
            throw new NotImplementedException();
        }
 
        public object ResetResource(object resource) {
            throw new NotImplementedException();
        }
 
        public object ResolveResource(object resource) {
            if (resource is Car) {
                _cars.Add((Car)resource);
            }
            return resource;
        }
 
        public void SaveChanges() {
            //We don't have any database/file to store the data in this test.            
        }
 
        public void SetReference(object targetResource, string propertyName, object propertyValue) {
            throw new NotImplementedException();
        }
 
        public void SetValue(object targetResource, string propertyName, object propertyValue) {
            if (propertyName == "Manufacturer") {
                ((Car)targetResource).Manufacturer = (string)propertyValue;
            } else if (propertyName == "ProductionYear") {
                ((Car)targetResource).ProductionYear = (string)propertyValue;
            } else {
                throw new Exception("Property not implemented.");
            }
        }
        #endregion
    }
}



This is not a reference implementation of IUpdatable. At the moment i have not found a good documentation/example. So i have tested a little bit. It works for testing purposes.

  • Add new item (to Styles-Folder) --> Web --> Style Sheet


  • Add css-Code
body 
{
    backgroundnone repeat scroll left top #EEEEEE;
    color#444444;
    font13px Arial,Tahoma,Helvetica,FreeSans,sans-serif;
}
 
div.entry 
{
    background-colorWhite;
    width300px;    
    border1px solid #444444;    
    padding2px;
    margin1px;
}
 
div.entry_delete 
{
    text-alignright;
}
 
div.buttonarea 
{
    text-alignright;
    width300px;    
}
 
div.inputfields 
{
    text-alignright;
    width300px;
}


Only some basic formatting...

  • Update odataaccess.js
The javascript-part is extended with examples for create and delete. I also have added a UI Clear-Function. You can just comment out the first line of the "RetrieveData" Method if you want to see the history of your actions. 
/*
* Retrieve data from service
**/
function RetrieveData() {
    ClearView();
    $.ajax({
        type: "GET",
        async: false,
        contentType: "application/json; charset=utf-8",
        datatype: "json",
        url: "/CarService.svc/Cars",
        success: RetrieveMultipleCallback,
        error: function () {
            alert("Error");
        },
        beforeSend: function (XMLHttpRequest) {
            XMLHttpRequest.setRequestHeader("Accept", "application/json;odata=verbose");
        }
    });
}

/*
* Handles the RetrieveMultiple Response
**/
function RetrieveMultipleCallback(data) {
    var $newDiv;
    $.each(data.d, function (i, item) {
        $newDiv = $("
" + "
" + item.Id + " - " + item.Manufacturer + " - " + item.ProductionYear + "
" + "
" + "
"); $("#listofcars").append($newDiv); }); $newDiv = $("
----
"); $("#listofcars").append($newDiv); } /* * Create data by service **/ function CreateData(id) { var car = {}; var man = $("#man_id").val(); var year = $("#year_id").val(); car.Manufacturer = man; car.ProductionYear = year; var carStringified = JSON.stringify(car); $.ajax({ type: "POST", async: false, data: carStringified, contentType: "application/json", datatype: "json", url: "/CarService.svc/Cars", success: CreateCallback, error: function () { alert("Error"); }, beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("Accept", "application/json"); } }); } /* * Handles the Create Response **/ function CreateCallback(data) { $("#man_id").val(""); $("#year_id").val(""); RetrieveData(); } /* * Delete data by service **/ function DeleteData(data) { $.ajax({ type: "DELETE", async: false, //data: carStringified, contentType: "application/json", datatype: "json", url: "/CarService.svc/Cars(" + data + ")", success: DeleteCallback, error: function () { alert("Error"); }, beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("Accept", "application/json"); } }); } /* * Handles the Delete Response **/ function DeleteCallback(data) { RetrieveData(); } /* * Clears the entry-view **/ function ClearView() { $("#listofcars").empty(); }
  • Run and test the Web Project
The List is empty if you start first. So just add some entries by yourself.


Cheers,
Markus

Wednesday, August 8, 2012

Part 1 - Consuming custom WCF Data Services (oData) Endpoint (Javascript/jQuery and c#)

The current version of the WCF Data Services 5.0 is working fine if you are in the .NET domain. Accessing with jQuery can cause some issues.

So here is one example using .NET (c#) and a second one using Javascript (jQuery).

If you want to view the final sourcecode, check it out from codeplex.
http://odataconsuming.codeplex.com/

You also can download the final solution as zip-File.
http://odataconsuming.codeplex.com/downloads/get/467041

You can find Part 2 of this documentation here.

Preparation

Creating Custom WCF Data Service
  • Create new Empty Web Application
  • Add Entity Framework (via NuGet)
  • Add jQuery (via NuGet)
  • Add new Item --> Web --> WCF Data Service (for oData V3)

  • Create entity-class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Services.Common;
 
namespace MK.oDataConsuming.Web {
 
    [DataServiceKey("Id")]
    public class Car {        
        public int Id { getset; }
        public string Manufacturer { getset; }
        public string ProductionYear { getset; }
    }
}
  • Create Context
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
 
namespace MK.oDataConsuming.Web.Model {
    public class CarContext  {
 
        private List<Car> _cars;
 
        public CarContext() {
            _cars = new List<Car>();
            _cars.Add(new Car() { Id = 1, Manufacturer = "BMW", ProductionYear = "1997" });
            _cars.Add(new Car() { Id = 2, Manufacturer = "Mercedes", ProductionYear = "2000" });
            _cars.Add(new Car() { Id = 3, Manufacturer = "Opel", ProductionYear = "2011" });
            _cars.Add(new Car() { Id = 4, Manufacturer = "Ford", ProductionYear = "1999" });
        }
 
        public IQueryable<Car> Cars {
            get {
                return _cars.AsQueryable<Car>();
            }
        }
    }
}
  • Configure access rules
using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;
 
using MK.oDataConsuming.Web.Model;
 
namespace MK.oDataConsuming.Web
{
    public class CarService : DataService<CarContext>
    {
        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("Cars"EntitySetRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
        }
    }
}
  • Set .svc as Start Page
  • Test Service



Create HTML Test-Page
  • Add new Item --> Web --> HTML Page
  • Add HTML-Code to file
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>oData Access</title>
    <script src="Scripts/jquery-1.7.2.min.js"></script>
    <script src="Scripts/json2.js"></script>
    <script src="Scripts/odataaccess.js"></script>
</head>
<body>
    <div>
        <input id="RetrieveButton" type="button" value="Retrieve" onclick="RetrieveData()" />
    </div>
    <div id="listofcars">    
    </div>
</body>
</html>
  • Add new Item (to Scripts-Folder) --> Web --> JScript File
  • Add Javascript Code to file
//Retrieve data from service
function RetrieveData() {
    $.ajax({
        type: "GET",
        async: false,
        contentType: "application/json; charset=utf-8",
        datatype: "json",
        url: "/CarService.svc/Cars",
        success: RetrieveMultipleCallback,
        error: function () {
            alert("Error");
        },
        beforeSend: function (XMLHttpRequest) {
            XMLHttpRequest.setRequestHeader("Accept""application/json;odata=verbose");
        }
    });
}
 
//Handles the RetrieveMultiple Response
function RetrieveMultipleCallback(data) {
    $.each(data.d, function (i, item) {
        var $newDiv = $("<div>" + item.Manufacturer + " - "  + item.ProductionYear + "</div>");
        $("#listofcars").append($newDiv);
    });
}

The Most important part ist the "beforeSend" Handler. Here we set the RequestHeader "Accept: application/json;odata=verbose". In older versions of WCF Data Services, this was "Accept: application/json" This does never work!
  • Test the Page


Create Console Application
  • Add new Console Project
  • Add Service Reference

  • Add Code to Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using MK.oDataConsuming.ConsoleApp.CarServiceReference;
 
namespace MK.oDataConsuming.ConsoleApp {
    class Program {
        static void Main(string[] args) {
 
            //Create Service Contexst
            CarContext context = new CarServiceReference.CarContext(new Uri("http://localhost:1184/CarService.svc"));
            
            //Create query, retrieve and convert to list
            var query = context.Cars.Where(c => c.Manufacturer == "Opel");
            List<Car> resultSet = query.ToList();
 
            //Check results
            if (resultSet != null && resultSet.Count > 0) {
                Car opel = resultSet[0];
                Console.WriteLine("{0}, {1}, {2}", opel.Id, opel.Manufacturer, opel.ProductionYear);
            } else {
                Console.WriteLine("Car not found.");
            }
 
            Console.Read();
        }
    }
}



  • Test the application

Cheers

Tuesday, July 17, 2012

My First NuGet Package

It's done. And it was quite easy.

There are enough great posts about creating this packages and the complete publishing process. So here are just some links i have used.



The most of them explain the creation via the nuget.exe command line application. There is also a nice UI available.



Cheers,
Markus

Project - PluginQuickDeploy Update 0.9.0.2

I have created a new version of pqd with minor changes.
Most important part is the NuGet Package.

Project Page: 
http://pqd.codeplex.com/ 

NuGet Package: 
https://nuget.org/packages/PluginQuickDeploy

NuGet Command: 
PM> Install-Package PluginQuickDeploy

Cheers, Markus

Wednesday, July 11, 2012

CRM 2011 - Accessing and filtering custom/crm WCF Data Service (oData) from sandboxed Plugin

As described in some MSDN-Forums and by Eric Pool, Quering data against the context directly via Linq in sandboxed plugins is not possible. You can retrieve the whole DataSet, but filtering is not possible.

If you need to access and filtering your results from an oData endpoint (also custom ones), you can use the following syntax.

Example OrganizationDataService (with FirstOrDefault):
//Define the Request URI
Guid accountId = new Guid("66BD3ABE-D2CA-E111-9E1F-000C29BDAB09");
string serviceAddress = @"http://crm:5555/DEV/XRMServices/2011/OrganizationData.svc/";
string accountQueryByPrimaryKey = string.Format("AccountSet(guid'{0}')", accountId.ToString());
Uri svcUri = new Uri(serviceAddress);
Uri accountUri = new Uri(string.Format("{0}{1}", serviceAddress, accountQueryByPrimaryKey));
 
//Create context and credentials ('My' is the name of the CRM organization, these classes are created when you add a service reference to CRM DataService endpoint)
CrmService.MyContext context = new CrmService.MyContext(svcUri);
context.Credentials = new System.Net.NetworkCredential("username""password""domain");
 
//Retrieve data
Account retrievedAccount = context.Execute<Account>(accountUri).FirstOrDefault();


Example Custom Service (with Enumeration):
// Define the Request URI
Uri svcUri = new Uri(@"http://crm:8888/MyDataService/CustomDataService.svc/");
Uri uriAccounts = new Uri(string.Format("{0}{1}", svcUri, "ACCOUNT(3)"));
 
// Create the context
DataServiceContext context = new DataServiceContext(svcUri)

// Enumerate over the query result.
StringBuilder sb = new StringBuilder();
foreach (ACCOUNT account in context.Execute<ACCOUNT>(uriAccounts)) { sb.AppendLine("Name: " + account.name); }

The trick is to use the oData (URI) for Filtering and to interprete just the results.

You can find the MSDN description here.

Friday, July 6, 2012

Project - PluginQuickDeploy

Today i have created my first project on Codeplex. Very nice page, the creation process was really easy and straight forward.

This small project was on my backlog for a very long time.

"PluginQuickDeploy" reduces the development time of Microsoft CRM Plugins, because the deployment to the test system is done by one click or directly in the build process (build events).

You can find the project here.

Saturday, March 10, 2012

Updating Database in EntityFramework "Code-First"

Today i have used EntityFramework "CodeFirst" for the first time.
I think it is a very interesting approach but the VisualStudio Integration could be more intuitive.

I have integrated it in an ASP.NET MVC Application. This task is described perfectly in a short video from Scott Hanselman.

What is missing in the video and took me some time, is the update of the database structure if the model has been changed (Well it is a MVC-Video, not an EF-Video). If you are using "Database First", you can simply update your .edmx file. In the "Code First" scenario we have to do some manual stuff. 

After you have created the application in the video (or your own EF-Based Application), run it and create some entries in the database. Than add an additional Property to one of your Model-Classes. For udpating the database structure run the following in the Package Manager Console.

PM> Enable-Migrations
PM> Add-Migration AddAppointmentSubject
PM> Update-Database

The "AddAppointmentSubject" can be named by your own. Here the structure is <task><class><property>.

There seems to be some issues/changes with the Version 5 (PreRelease) of EntityFramework. This stuff is running great on 4.3.1.

More details about updating the Model (e.g. new classes) you can find here.

Kind Regards,
Markus

Saturday, February 25, 2012

CRM 2011 - Create Custom Workflow Activities

Because the Release Preview Guide February 2012 contains the following statement,


CUSTOM WORKFLOW ACTIVITIES IN CRM ONLINE
With the  Microsoft Dynamics CRM Q2 service update, developers can build  custom  .NET  Workflow activity assemblies for Microsoft Dynamics CRM Online. These custom assemblies can be developed and deployed as part of a Microsoft Dynamics CRM solution package. Once a custom workflow activity has been deployed, it can then be utilized within the workflow engine for business process management. These new capabilities ensure parity in the developer experience between Microsoft Dynamics CRM Online  and on-premises. It also empowers organizations to bring external data, custom actions and integrations into their standard CRM business processes.


the Custom Workflow Activities could be interesting, also for product development in CRM 2011. Today, the Custom Workflow Activities are running in OnPremise Deployments but not in Online Systems. Because the Microsoft SDK Example is a little too confusing for a first approach, i have decided to create this small examle (based on the SDK-Stuff).

The Problem:
Create a Custom Workflow Activity, that creates a (follow up-)Task after an Account was created. (This is just an example. You can use default workflow functionality to reach this target.)


1. Create Solution with Project
The first step is to create a Visual Studio 2010 Project.
  • Select Visual C# -> Workflow -> Activity Library
  • Select .NET Framework 4
  • Select Name for Solution and Project
  • Select Location
Visual Studio Project
2. Check Project Settings
Check that the Framework-Type is NOT .NET Framework Client Profile. The correct one is .NET Framework 4.

Target framework (should be .NET Framework 4)
3. Create the Class
Delete the automatically created class Activity1.xaml and create a class called CreateTask.cs.

The new class

4. Adding References and Usings
Add references to the following assemblies (Rightclick References in the project and add...).
  • microsoft.xrm.sdk.dll
  • microsoft.xrm.sdk.workflow.dll
Add the reference for System.Runtime.Serialization, which is part of the .NET Framework.

Serialization
(Hint: The Add References window here is not the default VS2010 one. This one is part of the Productivity Power Tools. A (MUST HAVE-) Addon for Visual Studio 2010!)

Add the following using-Statements to your class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
 
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;


References and Usings
5. Inheritance
Make our class inherit from CodeActivity (System.Activities) and implement the method Execute. You also have to make the class public.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
 
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
 
namespace MK.CRM2011.CustomWorkflowStep {
    public class CreateTask : CodeActivity {
    }
}

6. Input Parameters
We would like to put a parameter in our Workflow. So every customer can define the subject of the task by it's own.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
 
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
 
namespace MK.CRM2011.CustomWorkflowStep {
    public class CreateTask : CodeActivity {
 
        #region Input Properties
        [Input("Task Subject")]
        [Default("Empty Subject")]
        public InArgument<string>TaskSubject{ getset; }
        #endregion
 
        protected override void Execute(CodeActivityContext context) {
            throw new NotImplementedException();
        }
    }
}

-------------------
The following code has to be added into the Execute-Method.

-------------------

7. Create Context and Service
The context we need for retrieving the ID of the current record, the service is required for communication with CRM-Webservice (IOrganizationService).

//Create the IWorkflowContext and the
//IOrganizationService for communication with CRM
IWorkflowContext workflowContext = 
    context.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory = 
    context.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service = 
    serviceFactory.CreateOrganizationService(workflowContext.UserId);


8. Business Logic
Now we have to add the logic which does the following.
  • Read Text from "TaskSubject" Input Parameter
string newSubject = TaskSubject.Get<string>(context);
  • Create a new task object
Entity newTask = new Entity("task");
  • Set subject of the task to TaskSubject value
newTask["subject"] = newSubject;
  • Set regardingobjectid of task to PrimaryEntityId of context (this is the id of the account).
newTask["regardingobjectid"] = 
    new EntityReference("account", workflowContext.PrimaryEntityId)
  • Create the new task in CRM
Guid taskId = service.Create(newTask);



9. Sign the assembly
First of all, go to project settings and sign your assembly.
Right Click the Project -> Properties -> Signing -> Sign the assembly -> New -> Enter Key file name "testkey" -> no password (for testing purposes).

Signing
10. Compile and deploy the assembly
Rebuild your solution and deploy the assembly to the CRM System. This can be done by the PluginRegistrationTool of microsoft. You can find this as part of the current CRM-SDK. You have to compile it by your own. Choose the following settings.

Deployment
11. Create a Workflow with the new step
Create a default Workflow in CRM. Choose the following settings.
Workflow basic configuration

Add the new step 
Push the SetProperties-Button of the step and select a default name for the Input Parameter (TaskSubject). Because the Owner of the new task should see which account was created, we add the name of the account as input parameter.

Set input parameter
Now save and don't forget to activate  the Workflow.
12. Testing
Create a new Account in CRM.
Create Account
Processing the workflow can take some seconds. Open the Activity List of the account and select Filter on "All". You should see our created task. The subject contains some static text and the name of the created account.
Activity View
13. Code Overview
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
 
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
 
namespace MK.CRM2011.CustomWorkflowStep {
    public class CreateTask : CodeActivity {
 
        #region Input Properties
        [Input("Task Subject")]
        [Default("Empty Subject")]
        public InArgument<string> TaskSubject { getset; }
        #endregion
 
        protected override void Execute(CodeActivityContext context) {
 
            //Create the IWorkflowContext and the
            //IOrganizationService for communication with CRM
            IWorkflowContext workflowContext =
              context.GetExtension<IWorkflowContext>();
            IOrganizationServiceFactory serviceFactory =
              context.GetExtension<IOrganizationServiceFactory>();
            IOrganizationService service =
              serviceFactory.CreateOrganizationService(workflowContext.UserId);
 
            //Retrieve data from InputParameter 
            string newSubject = TaskSubject.Get<string>(context);
 
            //Create the new task object (in memory)
            Entity newTask = new Entity("task");
            newTask["subject"] = newSubject;
            newTask["regardingobjectid"] =
              new EntityReference("account", workflowContext.PrimaryEntityId);
 
            //Create task in CRM
            Guid taskId = service.Create(newTask);
        }
    }
}

-------------------

Ok, this is more detailed than i have planned. Hope it helps!
Regards,
Markus