Storage for DataMappers in ASP.NET WebApplication - asp.net

In Martin Fowler's "Patterns of Enterprise Application Architecture"
is described approach for organizing DAL like a set of mappers for entities. Each has it's own IdentityMap storing specific entity.
for example in my ASP.NET WebApplication:
//AbstractMapper - superclass for all mappers in DAL
public abstract class AbstractMapper
{
private readonly string _connectionString;
protected string ConnectionString
{
get { return _connectionString; }
}
private readonly DbProviderFactory _dbFactory;
protected DbProviderFactory DBFactory
{
get { return _dbFactory; }
}
#region LoadedObjects (IdentityMap)
protected Hashtable LoadedObjects = new Hashtable();
public void RegisterObject(long id, DomainObject obj)
{
LoadedObjects[id] = obj;
}
public void UnregisterObject(long id)
{
LoadedObjects.Remove(id);
}
#endregion
public AbstractMapper(string connectionString, DbProviderFactory dbFactory)
{
_connectionString = connectionString;
_dbFactory = dbFactory;
}
protected virtual string DBTable
{
get
{
throw new NotImplementedException("database table is not defined in class " + this.GetType());
}
}
protected virtual T Find<T>(long id, IDbTransaction tr = null) where T : DomainObject
{
if (id == 0)
return null;
T result = (T)LoadedObjects[id];
if (result != null)
return result;
IDbConnection cn = GetConnection(tr);
IDbCommand cmd = CreateCommand(GetFindStatement(id), cn, tr);
IDataReader rs = null;
try
{
OpenConnection(cn, tr);
rs = cmd.ExecuteReader(CommandBehavior.SingleRow);
result = (rs.Read()) ? Load<T>(rs) : null;
}
catch (DbException ex)
{
throw new DALException("Error while loading an object by id in class " + this.GetType(), ex);
}
finally
{
CleanUpDBResources(cmd, cn, tr, rs);
}
return result;
}
protected virtual T Load<T>(IDataReader rs) where T : DomainObject
{
long id = GetReaderLong(rs["ID"]);
T result = (T)LoadedObjects[id];
if (result != null)
return result;
result = (T)DoLoad(id, rs);
RegisterObject(id, result);
return result;
}
// another CRUD here ...
}
// Specific Mapper for entity Account
public class AccountMapper : AbstractMapper
{
internal override string DBTable
{
get { return "Account"; }
}
public AccountMapper(string connectionString, DbProviderFactory dbFactory) : base(connectionString, dbFactory) { }
public Account Find(long id)
{
return Find<Account>(id);
}
public override DomainObject DoLoad(long id, IDataReader rs)
{
Account account = new Account(id);
account.Name = GetReaderString(rs["Name"]);
account.Value = GetReaderDecimal(rs["Value"]);
account.CurrencyID = GetReaderLong(rs["CurrencyID"]);
return account;
}
// ...
}
The question is: where to store these mappers? How system services (entities) should call mappers?
I decided to create MapperRegistry containing all mappers. So services can call mappers like:
public class AccountService : DomainService
{
public static Account FindAccount(long accountID)
{
if (accountID > 0)
return MapperRegistry.AccountMapper.Find(accountID);
return null;
}
...
}
But where can I store MapperRegistry instance? I see following variants, but don't like any of them:
MapperRegistry is global for application (Singleton)
Not applicable because of necessity of synchronization in multi-thread ASP.NET application (at least Martin says that only mad can choose this variant)
MapperRegistry per Session
Seems not so good too. All ORMs (NHibernate, LINQ to SQL, EntityFramework) masters advise to use DataContext (NHibernateSession, ObjectContext) per Request and not to store context in Session.
Also in my WebApp almost all requests are AJAX-requests to EntityController.asmx (with attribute ScriptService) returning JSON. And session is not allowed.
MapperRegistry per Request
There are a lot of separate AJAX calls. In this case life cycle of MapperRegistry will be too small. So the data almost always will be retrieved from database, as a result - low performance.
Dear Experts, please help me with architectural solution.

Related

Get Custom Attribute on ASMX Web Service from HTTP Module

Here's the situation:
Legacy ASP.NET product. A lot of old ASMX services (among other types of endpoints - ASPX, ASHX, etc).
We're enhancing some security logic. Part of the changes dictate defining the application module to which each ASMX service belongs. We plan to use the custom attribute shown below for this purpose.
[AttributeUsage(AttributeTargets.Class)]
public class ModuleAssignmentAttribute : Attribute
{
public Module[] Modules { get; set; }
public ModuleAssignmentAttribute(params Module[] modules)
{
Modules = modules;
}
}
Below is a sample of how the module will be applied to an ASMX service.
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ModuleAssignment(Module.ApplicationModuleA)]
public class SomeService : System.Web.Services.WebService
{
[WebMethod(true)]
public string GetValue()
{
return "Some Service Value";
}
}
The HTTP module below will be used to authorize access to the service.
public class MyAuthorizationModule : IHttpModule
{
public void Dispose()
{
//clean-up code here.
}
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(OnAuthorizeRequest);
}
public void OnAuthorizeRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Handler == null) return;
Attribute att = Attribute.GetCustomAttribute(HttpContext.Current.Handler.GetType(), typeof(ModuleAssignmentAttribute));
if (att != null)
{
Module[] modules = ((ModuleAssignmentAttribute)att).Modules;
// Simulate getting the user's active role ID
int roleId = 1;
// Simulate performing an authz check
AuthorizationAgent agent = new AuthorizationAgent();
bool authorized = agent.AuthorizeRequest(roleId, modules);
if (!authorized)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.StatusCode = 401;
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
}
}
The problem is that, for ASMX web services, the following line of code from the HTTP module returns null (note that this works for ASPX pages).
Attribute att = Attribute.GetCustomAttribute(HttpContext.Current.Handler.GetType(), typeof(ModuleAssignmentAttribute));
The value of HttpContext.Current.Handler.GetType() in this situation is "System.Web.Script.Services.ScriptHandlerFactory+HandlerWrapperWithSession". That type is apparently unaware of the custom attribute defined on the ASMX service.
Any ideas on how to get the custom attribute from the ASMX service type in this scenario?
Here's a solution to the problem. Requires reflection. Ugly and fragile code - I wouldn't recommend using it if you don't have to. I'd be interested to know if I'm overlooking a more elegant way.
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(OnAuthorizeRequest);
}
public void OnAuthorizeRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Handler == null) return;
Attribute att = null;
// ScriptHandlerFactory+HandlerWrapperWithSession is the type of handler for ASMX web service calls to web methods that use session.
// This class is internal, so need to do a string comparison here (is there another way?).
if (HttpContext.Current.Handler.GetType().ToString() == "System.Web.Script.Services.ScriptHandlerFactory+HandlerWrapperWithSession")
{
// HandlerWrapperWithSession has a protected field named "_originalHandler" that it inherits from HandlerWrapper.
FieldInfo originalHandlerField = HttpContext.Current.Handler.GetType().GetField("_originalHandler",BindingFlags.NonPublic | BindingFlags.Instance);
object originalHandler = originalHandlerField.GetValue(HttpContext.Current.Handler);
// The _originalHandler value is an instance of SyncSessionHandler.
// The inheritance tree for SyncSessionHandler is:
//
// WebServiceHandler
// ----> SyncSessionlessHandler
// ----> SyncSessionHandler
//
// We need to walk the tree up to the WebServiceHandler class.
bool exitLoop = false;
Type t = originalHandler.GetType();
while (t != null)
{
// WebServiceHandler is internal, so again another string comparison.
if (t.ToString() == "System.Web.Services.Protocols.WebServiceHandler")
{
// WebServiceHandler has a private field named protocol. This field has the type HttpGetServerProtocol.
FieldInfo protocolField = t.GetField("protocol", BindingFlags.NonPublic | BindingFlags.Instance);
object protocolValue = protocolField.GetValue(originalHandler);
// The inheritance tree for ServerProtocol is:
//
// HttpServerProtocol
// ----> HttpGetServerProtocol
//
// We need to walk the three up to the HttpServerProtocol class.
Type t2 = protocolValue.GetType();
while (t2 != null)
{
if (t2.ToString() == "System.Web.Services.Protocols.HttpServerProtocol")
{
// HttpServerProtocol has an internal property named MethodInfo. This property has the type LogicalMethodInfo.
PropertyInfo methodInfoProperty = t2.GetProperty("MethodInfo", BindingFlags.NonPublic | BindingFlags.Instance);
object methodInfoValue = methodInfoProperty.GetValue(protocolValue);
// The LogicalMethodInfo class has a DeclaringType property. This property stores the type of the ASMX service.
att = Attribute.GetCustomAttribute(((LogicalMethodInfo)methodInfoValue).DeclaringType, typeof(ModuleAssignmentAttribute));
exitLoop = true;
break;
}
t2 = t2.BaseType;
}
}
if (exitLoop) break;
t = t.BaseType;
}
}
else
{
att = Attribute.GetCustomAttribute(HttpContext.Current.Handler.GetType(), typeof(ModuleAssignmentAttribute));
}
if (att != null)
{
Module[] modules = ((ModuleAssignmentAttribute)att).Modules;
// Simulate getting the user's active role ID
int roleId = 1;
// Simulate performing an authz check
AuthorizationAgent agent = new AuthorizationAgent();
bool authorized = agent.AuthorizeRequest(roleId, modules);
if (!authorized)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.StatusCode = 401;
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
}
}
I had to use Reflector + runtime debugging to find this solution.

How to call EJB from another app on the same server?

I have java SE sample client which run on desktop (code below). But I have access to WebSphere were called EJB is deployed. How to rewrite below code to work on WebSphere? (When I leave this code just like it is program works but I think this can be done more simple and clear)
Main method:
WSConn connection = new WSConn();
final Plan plan = connection.getPlanBean();
com.ibm.websphere.security.auth.WSSubject.doAs(connection.getSubject(), new java.security.PrivilegedAction<Object>() {
public Object run() {
try {
// App logic
} catch (Throwable t) {
System.err.println("PrivilegedAction - Error calling EJB: " + t);
t.printStackTrace();
}
return null;
}
}); // end doAs
WSConn class:
public class WSConn {
private static final String INITIAL_CONTEXT_FACTORY = "com.ibm.websphere.naming.WsnInitialContextFactory";
private static final String JAAS_MODULE = "WSLogin";
private static final String MODEL_EJB_NAME_LONG = "ejb/com/ibm/ModelHome";
private static final String PLAN_EJB_NAME_LONG = "ejb/com/ibm/PlanHome";
private Subject subject;
private InitialContext initialContext;
private String serverName;
private String serverPort;
private String uid;
private String pwd;
private String remoteServerName;
private Model modelBean;
private Plan planBean;
public WSConn() {
Properties props = new Properties();
try {
props.load(WSConn.class.getClassLoader().getResourceAsStream("WSConn.properties"));
} catch (IOException e) {
e.printStackTrace();
}
serverName = props.getProperty("WSConn.serverName");
serverPort = props.getProperty("WSConn.serverPort");
uid = props.getProperty("WSConn.userID");
pwd = props.getProperty("WSConn.password");
remoteServerName = props.getProperty("WSConn.remoteServerName");
}
private void init() {
if (subject == null || initialContext == null) {
subject = login();
}
}
private Subject login() {
Subject subject = null;
try {
LoginContext lc = null;
// CRATE LOGIN CONTEXT
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
env.put(Context.PROVIDER_URL, "corbaloc:iiop:" + serverName + ":" + serverPort);
initialContext = new InitialContext(env);
// Just to test the connection
initialContext.lookup("");
lc = new LoginContext(JAAS_MODULE, new WSCallbackHandlerImpl(uid, pwd));
lc.login();
subject = lc.getSubject();
} catch (javax.naming.NoPermissionException exc) {
System.err.println("[WSConn] - Login Error: " + exc);
} catch (Exception exc) {
System.err.println("[WSConn] - Error: " + exc);
}
return subject;
}
public wModel getModelBean() {
if (modelBean == null) {
init();
modelBean = (wModel) com.ibm.websphere.security.auth.WSSubject.doAs(subject,
new java.security.PrivilegedAction<wModel>() {
public wModel run() {
wModel session = null;
try {
Object o = initialContext.lookup(MODEL_EJB_NAME_LONG);
wModelHome home = (wModelHome) PortableRemoteObject.narrow(o, wModelHome.class);
if (home != null) {
session = home.create(remoteServerName);
}
} catch (Exception exc) {
System.err.println("Error getting model bean: " + exc);
}
return session;
}
}); // end doAs
}
return modelBean;
}
public wPlan getPlanBean() {
if (planBean == null) {
init();
planBean = (wPlan) com.ibm.websphere.security.auth.WSSubject.doAs(subject,
new java.security.PrivilegedAction<wPlan>() {
public wPlan run() {
wPlan session = null;
try {
Object o = initialContext.lookup(PLAN_EJB_NAME_LONG);
wPlanHome home = (wPlanHome) PortableRemoteObject.narrow(o, wPlanHome.class);
if (home != null) {
session = home.create(remoteServerName);
}
} catch (Exception exc) {
System.err.println("Error getting plan bean: " + exc);
}
return session;
}
}); // end doAs
}
return planBean;
}
public Subject getSubject() {
if (subject == null) {
init();
}
return subject;
}
}
As indicated in another answer, the classic mechanism is to lookup and narrow the home interface.
Get the initial context
final InitialContext initialContext = new InitialContext();
Lookup for the home by jndi name, specifying either the full jndi name
Object obj = initialContext.lookup("ejb/com/ibm/tws/conn/plan/ConnPlanHome");
or you can create e reference in your WAR and use java:comp/env/yourname
Then narrow the home to the home interface class
ConnPlanHome planHome = (ConnPlanHome)PortableRemoteObject.narrow(obj, ConnPlanHome.class);
and then create the EJB remote interface
ConnPlan plan = planHome.create();
The about calls should work for IBM Workload Scheduler distributed.
For IBM Workload Scheduler z/OS the JNDI name and the class names are different:
final InitialContext initialContext = new InitialContext();
String engineName = "XXXX";
Object obj = initialContext.lookup("ejb/com/ibm/tws/zconn/plan/ZConnPlanHome");
ZConnPlanHome planHome = (ZConnPlanHome)PortableRemoteObject.narrow(obj, ZConnPlanHome.class);
ZConnPlan plan = planHome.create(engineName);
User credentials are propagated from the client to the engine, the client need to be authenticated otherwise the engine will reject the request.
If you're trying to access an EJB from a POJO class, then there is nothing more simple than lookup+narrow. However, if the POJO is included in an application (EAR or WAR), then you could declare and lookup an EJB reference (java:comp/ejb/myEJB), and then the container would perform the narrow rather than your code. If you change your code to be a managed class like a servlet, another EJB, or a CDI bean, then you could use #EJB injection, and then you would not even need a lookup.

Using one session per request, how to handle updating child objects

I'm having some serious issues with Fluent Nhibernate in my ASP.NET WebForms app when trying to modify a child object and then saving the parent object.
My solution is currently made of 2 projects :
Core : A class library where all entities & repositories classes are located
Website : The ASP.NET 4.5 WebForms application
Here is my simple mapping for my Employee object:
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.DateCreated);
Map(x => x.Username);
Map(x => x.FirstName);
Map(x => x.LastName);
HasMany(x => x.TimeEntries).Inverse().Cascade.All().KeyColumn("Employee_id");
}
}
Here is my my mapping for the TimeEntry object:
public class TimeEntryMap : ClassMap<TimeEntry>
{
public TimeEntryMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.DateCreated);
Map(x => x.Date);
Map(x => x.Length);
References(x => x.Employee).Column("Employee_id").Not.Nullable();
}
}
As stated in the title, i'm using one session per request in my web app, using this code in Gobal.asax:
public static ISessionFactory SessionFactory = Core.SessionFactoryManager.CreateSessionFactory();
public static ISession CurrentSession
{
get { return (ISession)HttpContext.Current.Items["current.session"]; }
set { HttpContext.Current.Items["current.session"] = value; }
}
protected Global()
{
BeginRequest += delegate
{
System.Diagnostics.Debug.WriteLine("New Session");
CurrentSession = SessionFactory.OpenSession();
};
EndRequest += delegate
{
if (CurrentSession != null)
CurrentSession.Dispose();
};
}
Also, here is my SessionFactoryManager class:
public class SessionFactoryManager
{
public static ISession CurrentSession;
public static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("Website.Properties.Settings.WebSiteConnString")))
.Mappings(m => m
.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
.BuildSessionFactory();
}
public static ISession GetSession()
{
return (ISession)HttpContext.Current.Items["current.session"];
}
}
Here is one of my repository class, the one i use to handle the Employee's object data operations:
public class EmployeeRepository<T> : IRepository<T> where T : Employee
{
private readonly ISession _session;
public EmployeeRepository(ISession session)
{
_session = session;
}
public T GetById(int id)
{
T result = null;
using (ITransaction tx = _session.BeginTransaction())
{
result = _session.Get<T>(id);
tx.Commit();
}
return result;
}
public IList<T> GetAll()
{
IList<T> result = null;
using (ITransaction tx = _session.BeginTransaction())
{
result = _session.Query<T>().ToList();
tx.Commit();
}
return result;
}
public bool Save(T item)
{
var result = false;
using (ITransaction tx = _session.BeginTransaction())
{
_session.SaveOrUpdate(item);
tx.Commit();
result = true;
}
return result;
}
public bool Delete(T item)
{
var result = false;
using (ITransaction tx = _session.BeginTransaction())
{
_session.Delete(_session.Load(typeof (T), item.Id));
tx.Commit();
result = true;
}
return result;
}
public int Count()
{
var result = 0;
using (ITransaction tx = _session.BeginTransaction())
{
result = _session.Query<T>().Count();
tx.Commit();
}
return result;
}
}
Now, here is my problem. When i'm trying to insert Employee(s), everything is fine. Updating is also perfect... well, as long as i'm not updating one of the TimeEntry object referenced in the "TimeEntries" property of Employee...
Here is where an exception is raised (in a ASPX file of the web project):
var emp = new Employee(1);
foreach (var timeEntry in emp.TimeEntries)
{
timeEntry.Length += 1;
}
emp.Save();
Here is the exception that is raised:
[NonUniqueObjectException: a different object with the same identifier
value was already associated with the session: 1, of entity:
Core.Entities.Employee]
Basically, whenever I try to
Load an employee and
Modify one of the saved TimeEntry, I get that exception.
FYI, I tried replacing the SaveOrUpdate() in the repository for Merge(). It did an excellent job, but when creating an object using Merge(), my object never gets it's Id set.
I also tried creating and flushing the ISession in each function of my repository. It made no sense because as soon as i was trying to load the TimeEntries property of an Employee, an exception was raised, saying the object could not be lazy-loaded as the ISession was closed...
I'm at lost and would appreciate some help. Any suggestion for my repository is also welcome, as i'm quite new to this.
Thanks you guys!
This code
var emp = new Employee(1);
foreach (var timeEntry in emp.TimeEntries)
{
timeEntry.Length += 1;
}
emp.Save();
is creating a new Employee object, presumable with an ID of 1 passed through the constructor. You should be loading the Employee from the database, and your Employee object should not allow the ID to be set since you are using an identity column. Also, a new Employee would not have any TimeEntries and the error message clearly points to an Employee instance as the problem.
I'm not a fan of transactions inside repositories and I'm really not a fan of generic repositories. Why is your EmployeeRepository a generic? Shouldn't it be
public class EmployeeRepository : IRepository<Employee>
I think your code should look something like:
var repository = new EmployeeRepository(session);
var emp = repository.GetById(1);
foreach (var timeEntry in emp.TimeEntries)
{
timeEntry.Length += 1;
}
repository.Save(emp);
Personally I prefer to work directly with the ISession:
using (var txn = _session.BeginTransaction())
{
var emp = _session.Get<Employee>(1);
foreach (var timeEntry in emp.TimeEntries)
{
timeEntry.Length += 1;
}
txn.Commit();
}
This StackOverflow Answer gives an excellent description of using merge.
But...
I believe that you are facing issues with setting up a correct session pattern for your application.
I you suggest to take a look at session-per-request pattern
where in you create a single NHibernate session object per request. the session is opened when the request is received and closed/flushed on generating a response.
Also make sure that instead of using SessionFactory.OpenSession() to get a session try using SessionFactory.GetCurrentSession() which puts the onus on NHibernate to return you the current correct session.
I hope this pushes you in the right direction.

How can i use engine object in my console application

"How can i use engine in my console application"
I shouldn't use the ITemplate-interface and Transform-Method.
I am using Tridion 2011
Could anyone please suggest me.
You can't. The Engine class is part of the TOM.NET and that API is explicitly reserved for use in:
Template Building Blocks
Event Handlers
For all other cases (such as console applications) you should use the Core Service.
There are many good questions (and articles on other web sites) already:
https://stackoverflow.com/search?q=%5Btridion%5D+core+service
http://www.google.com/#q=tridion+core+service
If you get stuck along the way, show us the relevant code+configuration you have and what error message your get (or at what step you are stuck) and we'll try to help from there.
From a console application you should use the Core Service. I wrote a small example using the Core Service to search for items in the content manager.
Console.WriteLine("FullTextQuery:");
var fullTextQuery = Console.ReadLine();
if (String.IsNullOrWhiteSpace(fullTextQuery) || fullTextQuery.Equals(":q", StringComparison.OrdinalIgnoreCase))
{
break;
}
Console.WriteLine("SearchIn IdRef:");
var searchInIdRef = Console.ReadLine();
var queryData = new SearchQueryData
{
FullTextQuery = fullTextQuery,
SearchIn = new LinkToIdentifiableObjectData
{
IdRef = searchInIdRef
}
};
var results = coreServiceClient.GetSearchResults(queryData);
results.ToList().ForEach(result => Console.WriteLine("{0} ({1})", result.Title, result.Id));
Add a reference to Tridion.ContentManager.CoreService.Client to your Visual Studio Project.
Code of the Core Service Client Provider:
public interface ICoreServiceProvider
{
CoreServiceClient GetCoreServiceClient();
}
public class CoreServiceDefaultProvider : ICoreServiceProvider
{
private CoreServiceClient _client;
public CoreServiceClient GetCoreServiceClient()
{
return _client ?? (_client = new CoreServiceClient());
}
}
And the client itself:
public class CoreServiceClient : IDisposable
{
public SessionAwareCoreServiceClient ProxyClient;
private const string DefaultEndpointName = "netTcp_2011";
public CoreServiceClient(string endPointName)
{
if(string.IsNullOrWhiteSpace(endPointName))
{
throw new ArgumentNullException("endPointName", "EndPointName is not specified.");
}
ProxyClient = new SessionAwareCoreServiceClient(endPointName);
}
public CoreServiceClient() : this(DefaultEndpointName) { }
public string GetApiVersionNumber()
{
return ProxyClient.GetApiVersion();
}
public IdentifiableObjectData[] GetSearchResults(SearchQueryData filter)
{
return ProxyClient.GetSearchResults(filter);
}
public IdentifiableObjectData Read(string id)
{
return ProxyClient.Read(id, new ReadOptions());
}
public ApplicationData ReadApplicationData(string subjectId, string applicationId)
{
return ProxyClient.ReadApplicationData(subjectId, applicationId);
}
public void Dispose()
{
if (ProxyClient.State == CommunicationState.Faulted)
{
ProxyClient.Abort();
}
else
{
ProxyClient.Close();
}
}
}
When you want to perform CRUD actions through the core service you can implement the following methods in the client:
public IdentifiableObjectData CreateItem(IdentifiableObjectData data)
{
data = ProxyClient.Create(data, new ReadOptions());
return data;
}
public IdentifiableObjectData UpdateItem(IdentifiableObjectData data)
{
data = ProxyClient.Update(data, new ReadOptions());
return data;
}
public IdentifiableObjectData ReadItem(string id)
{
return ProxyClient.Read(id, new ReadOptions());
}
To construct a data object of e.g. a Component you can implement a Component Builder class that implements a create method that does this for you:
public ComponentData Create(string folderUri, string title, string content)
{
var data = new ComponentData()
{
Id = "tcm:0-0-0",
Title = title,
Content = content,
LocationInfo = new LocationInfo()
};
data.LocationInfo.OrganizationalItem = new LinkToOrganizationalItemData
{
IdRef = folderUri
};
using (CoreServiceClient client = provider.GetCoreServiceClient())
{
data = (ComponentData)client.CreateItem(data);
}
return data;
}
Hope this gets you started.

HOW to get Context of WorkflowApplication?

I'm making a Workflow designer similar to Visual Workflow Tracking*.
I would like add a new control to debug wf, but id don't know how to access to wf context
To Run WF I Use this :
WFApplication wfApp = new WorkflowApplication(act, inputs);
My idea is when i recive trace event, get context of wfApp, to get vars or arguments values.
It's posibble?
*You can donwload VisualStudioTracking Code From :
Windows Communication Foundation (WCF) and Windows Workflow Foundation (WF) Samples for .NET Framework 4
and then
:\WF_WCF_Samples\WF\Application\VisualWorkflowTracking*
Finally I solved the problem.
First I get all arguments names, and variables of workflow, from xaml.
class XamlHelper
{
private string xaml;
public XamlHelper(string xaml)
{
this.xaml = xaml;
DynamicActivity act = GetRuntimeExecutionRoot(this.xaml);
ArgumentNames = GetArgumentsNames(act);
GetVariables(act);
}
private void GetVariables(DynamicActivity act)
{
Variables = new List<string>();
InspectActivity(act);
}
private void InspectActivity(Activity root)
{
IEnumerator<Activity> activities =
WorkflowInspectionServices.GetActivities(root).GetEnumerator();
while (activities.MoveNext())
{
PropertyInfo propVars = activities.Current.GetType().GetProperties().FirstOrDefault(p => p.Name == "Variables" && p.PropertyType == typeof(Collection<Variable>));
if (propVars != null)
{
try
{
Collection<Variable> variables = (Collection<Variable>)propVars.GetValue(activities.Current, null);
variables.ToList().ForEach(v =>
{
Variables.Add(v.Name);
});
}
catch
{
}
}
InspectActivity(activities.Current);
}
}
public List<string> Variables
{
get;
private set;
}
public List<string> ArgumentNames
{
get;
private set;
}
private DynamicActivity GetRuntimeExecutionRoot(string xaml)
{
Activity root = ActivityXamlServices.Load(new StringReader(xaml));
WorkflowInspectionServices.CacheMetadata(root);
return root as DynamicActivity;
}
private List<string> GetArgumentsNames(DynamicActivity act)
{
List<string> names = new List<string>();
if (act != null)
{
act.Properties.Where(p => typeof(Argument).IsAssignableFrom(p.Type)).ToList().ForEach(dp =>
{
names.Add(dp.Name);
});
}
return names;
}
}
Second I create trace with these arguments and variable names
private static WFTrace CreateTrace(List<string> argumentNames, List<string> variablesNames)
{
WFTrace trace = new WFTrace();
trace.TrackingProfile = new TrackingProfile()
{
ImplementationVisibility = ImplementationVisibility.All,
Name = "CustomTrackingProfile",
Queries =
{
new CustomTrackingQuery()
{
Name = all,
ActivityName = all
},
new WorkflowInstanceQuery()
{
// Limit workflow instance tracking records for started and completed workflow states
States = {WorkflowInstanceStates.Started, WorkflowInstanceStates.Completed },
}
}
};
trace.TrackingProfile.Queries.Add(GetActivityQueryState(argumentNames, variablesNames));
return trace;
}
And then invoke wf and add traceextension.
Adam this is the code
private TrackingQuery GetActivityQueryState(List<string> argumentNames, List<string> variablesNames)
{
ActivityStateQuery query = new ActivityStateQuery()
{
ActivityName = "*",
States = { ActivityStates.Executing, ActivityStates.Closed }
};
if (argumentNames != null)
{
argumentNames.Distinct().ToList().ForEach(arg =>
{
query.Arguments.Add(arg);
});
}
if (variablesNames != null)
{
variablesNames.Distinct().ToList().ForEach(v =>
{
query.Variables.Add(v);
});
}
return query;
}
You can use a Tracking Participants to extract the variables and arguments when the workflow is running.
I try tracking variables with this tracking participant
private static VisualTrackingParticipant VisualTracking()
{
String all = "*";
VisualTrackingParticipant simTracker = new VisualTrackingParticipant()
{
TrackingProfile = new TrackingProfile()
{
Name = "CustomTrackingProfile",
Queries =
{
new CustomTrackingQuery()
{
Name = all,
ActivityName = all
},
new WorkflowInstanceQuery()
{
// Limit workflow instance tracking records for started and completed workflow states
// States = { WorkflowInstanceStates.Started, WorkflowInstanceStates.Completed },
States={all}
},
new ActivityStateQuery()
{
// Subscribe for track records from all activities for all states
ActivityName = all,
States = { all },
// Extract workflow variables and arguments as a part of the activity tracking record
// VariableName = "*" allows for extraction of all variables in the scope
// of the activity
Variables =
{
{ all }
},
Arguments={ {all}}
}
}
}
};
return simTracker;
}
and this is VisualTrackingParticipant
public class VisualTrackingParticipant : System.Activities.Tracking.TrackingParticipant
{
public event EventHandler<TrackingEventArgs> TrackingRecordReceived;
public Dictionary<string, Activity> ActivityIdToWorkflowElementMap { get; set; }
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
OnTrackingRecordReceived(record, timeout);
}
//On Tracing Record Received call the TrackingRecordReceived with the record received information from the TrackingParticipant.
//We also do not worry about Expressions' tracking data
protected void OnTrackingRecordReceived(TrackingRecord record, TimeSpan timeout)
{
System.Diagnostics.Debug.WriteLine(
String.Format("Tracking Record Received: {0} with timeout: {1} seconds.", record, timeout.TotalSeconds)
);
if (TrackingRecordReceived != null)
{
ActivityStateRecord activityStateRecord = record as ActivityStateRecord;
if//(
(activityStateRecord != null)
// && (!activityStateRecord.Activity.TypeName.Contains("System.Activities.Expressions")))
{
Activity act = null;
ActivityIdToWorkflowElementMap.TryGetValue(activityStateRecord.Activity.Id, out act);
TrackingRecordReceived(this, new TrackingEventArgs(
record,
timeout,
act
)
);
}
else
{
TrackingRecordReceived(this, new TrackingEventArgs(record, timeout, null));
}
}
}
}
//Custom Tracking EventArgs
public class TrackingEventArgs : EventArgs
{
public TrackingRecord Record {get; set;}
public TimeSpan Timeout {get; set;}
public Activity Activity { get; set; }
public TrackingEventArgs(TrackingRecord trackingRecord, TimeSpan timeout, Activity activity)
{
this.Record = trackingRecord;
this.Timeout = timeout;
this.Activity = activity;
}
}
if I trace this wf :
<Activity mc:Ignorable="sap" x:Class="Microsoft.Samples.Workflow" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mv="clr-namespace:Microsoft.VisualBasic;assembly=System" xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:s1="clr-namespace:System;assembly=System" xmlns:s2="clr-namespace:System;assembly=System.Xml" xmlns:s3="clr-namespace:System;assembly=System.Core" xmlns:sa="clr-namespace:System.Activities;assembly=System.Activities" xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=System" xmlns:scg1="clr-namespace:System.Collections.Generic;assembly=System.ServiceModel" xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System.Core" xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sd="clr-namespace:System.Data;assembly=System.Data" xmlns:sd1="clr-namespace:System.Data;assembly=System.Data.DataSetExtensions" xmlns:sl="clr-namespace:System.Linq;assembly=System.Core" xmlns:st="clr-namespace:System.Text;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<x:Members>
<x:Property Name="decisionVar" Type="InArgument(x:Boolean)" />
</x:Members>
<sap:VirtualizedContainerService.HintSize>666,676</sap:VirtualizedContainerService.HintSize>
<mva:VisualBasic.Settings>Assembly references and imported namespaces serialized as XML namespaces</mva:VisualBasic.Settings>
<Flowchart sad:XamlDebuggerXmlReader.FileName="C:\WF_WCF_Samples\WF\Application\VisualWorkflowTracking\CS\VisualWorkflowTracking\Workflow.xaml" sap:VirtualizedContainerService.HintSize="626,636" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces">
<Flowchart.Variables>
<Variable x:TypeArguments="x:String" Name="myStr" />
</Flowchart.Variables>
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">False</x:Boolean>
<av:Point x:Key="ShapeLocation">270,7.5</av:Point>
<av:Size x:Key="ShapeSize">60,75</av:Size>
<av:PointCollection x:Key="ConnectorLocation">300,82.5 300,111.5</av:PointCollection>
<x:Double x:Key="Width">611.5</x:Double>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<Flowchart.StartNode>
<FlowStep x:Name="__ReferenceID0">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">179,111.5</av:Point>
<av:Size x:Key="ShapeSize">242,58</av:Size>
<av:PointCollection x:Key="ConnectorLocation">300,169.5 300,199.5 280,199.5 280,269.5</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<Assign sap:VirtualizedContainerService.HintSize="242,58">
<Assign.To>
<OutArgument x:TypeArguments="x:String">[myStr]</OutArgument>
</Assign.To>
<Assign.Value>
<InArgument x:TypeArguments="x:String">PDC Rocks</InArgument>
</Assign.Value>
</Assign>
<FlowStep.Next>
<FlowStep x:Name="__ReferenceID1">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">174.5,269.5</av:Point>
<av:Size x:Key="ShapeSize">211,61</av:Size>
<av:PointCollection x:Key="ConnectorLocation">155.33,351.361666666667 155.33,481 204.5,481</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<WriteLine sap:VirtualizedContainerService.HintSize="211,61" Text="[myStr]" />
</FlowStep>
</FlowStep.Next>
</FlowStep>
</Flowchart.StartNode>
<x:Reference>__ReferenceID0</x:Reference>
<x:Reference>__ReferenceID1</x:Reference>
</Flowchart>
</Activity>
When activitity assing close y recive this trace this args
[Arg] Value = PDC Rocks
[Arg] To = PDC Rocks
and I don't know the name of var where value is assined

Resources