Wednesday, 7 March 2012
Nitin's blog: How to Access ApplicationResources in Windows host...
Nitin's blog: How to Access ApplicationResources in Windows host...: Scenerio : - If you have a WPF application and you are running it standalone, you can see all static as well as dynamic resources like Image...
How to Access ApplicationResources in Windows hosted WPF application
Scenerio: - If you have a WPF application and you are running it standalone, you can see all static as well as dynamic resources like Images, converters etc. When you host this it to native Win32 or Windows application all images vanish from the UI.
Reason: - The reason is very simple. When you run WPF application, there's a class called App.xaml (app.xaml.cs) which is the entry point of any WPF application during build. Apparently, this class does not has any information how to run the application, But if you goto App.g.cs in obj folder of WPF app, you can see what I mean.
When you host WPF app to windows app, MSbuild knows nothing about how to set the entry point of WPF application rather its set the entry point of windows app which is obvious.
Solution: - Load the WPF application resources at runtime. Open the usercontrol.cs (it could be any form or control) file where your are hosting WPF control inside ElementHost. Now, after initializeComponent() in the constructor load the resource dictionary using the following code.
assemblyName is the name of your assembly. BamlReader is a static class used to load resources from stream
public static class BamlReader
{
public static object Load(Stream stream)
{
var pc = new ParserContext();
MethodInfo loadBamlMethod = typeof (XamlReader).GetMethod("LoadBaml",
BindingFlags.NonPublic | BindingFlags.Static);
return loadBamlMethod.Invoke(null, new object[] { stream, pc, null, false });
}
}
So now, we have resources with us, we need to set it to the appropriate control
var designerView = new WorkflowDesignerView();
designerView.Resources.MergedDictionaries.Add(resources);
Where designerView is the WPF control you are hosting in windows app.
Lastly add this designer to Elementhost as a child
elementHost1.Child = designerView;
and you're done. Now you can see all images & converters are available and working properly.
Reason: - The reason is very simple. When you run WPF application, there's a class called App.xaml (app.xaml.cs) which is the entry point of any WPF application during build. Apparently, this class does not has any information how to run the application, But if you goto App.g.cs in obj folder of WPF app, you can see what I mean.
When you host WPF app to windows app, MSbuild knows nothing about how to set the entry point of WPF application rather its set the entry point of windows app which is obvious.
Solution: - Load the WPF application resources at runtime. Open the usercontrol.cs (it could be any form or control) file where your are hosting WPF control inside ElementHost. Now, after initializeComponent() in the constructor load the resource dictionary using the following code.
StreamResourceInfo sri = System.Windows.Application.GetResourceStream(
new Uri("/assemblyName;component/Resources/ButtonStyles.xaml", UriKind.Relative));
var resources = (ResourceDictionary)BamlReader.Load(sri.Stream);
assemblyName is the name of your assembly. BamlReader is a static class used to load resources from stream
public static class BamlReader
{
public static object Load(Stream stream)
{
var pc = new ParserContext();
MethodInfo loadBamlMethod = typeof (XamlReader).GetMethod("LoadBaml",
BindingFlags.NonPublic | BindingFlags.Static);
return loadBamlMethod.Invoke(null, new object[] { stream, pc, null, false });
}
}
So now, we have resources with us, we need to set it to the appropriate control
var designerView = new WorkflowDesignerView();
designerView.Resources.MergedDictionaries.Add(resources);
Where designerView is the WPF control you are hosting in windows app.
Lastly add this designer to Elementhost as a child
elementHost1.Child = designerView;
and you're done. Now you can see all images & converters are available and working properly.
Tuesday, 6 March 2012
Nitin's blog: Host Workflows stored in database as WCF services ...
Nitin's blog: Host Workflows stored in database as WCF services ...: Today I am going to show you how to host WF4 workflows in WCF which are stored in database. We'll be using ASP.Net MVC as the solution proje...
Thursday, 23 February 2012
How to register third party assemblies into GAC
Today, I'll present how to register the third party dll files into GAC. If you try to do so, you'll get an error saying that assembly should be strongly named. The problem is because the referenced third party dll is not signed or strongly named.
Well, here are the necessary steps how to proceed: -
- Open the Visual Studio Command Prompt. I know everyone can do it.
- Copy the dlls you want to register into the VC folder. Usually it is "c:\Program Files (x86)\microsoft visual studio 10.0\vc" in windows7. It is better to copy the dll in into the VC folder otherwise you need to provide the full path of the assembly.
- Create a key pair to sign the assembly – sn.exe –k key.snk . If you are not giving the path of the strong name file, it will be generated in VC folder.
- Disassemble the dll using ILDASM – ILDASM.exe Yourdll.dll /out=Yourdll.il . This will create a ‘Yourdll.il’ file containing the IL for the assembly. It will also extract the assembly so you will now have a folder with lots of files, you will also see a .res file containing the resources for the dll.
- Reassemble the dll using ILASM to sign the output with the key created above –ILASM.exe Yourdll.il /dll /out=Yourdll2.dll /key=key.snk
You should now have a signed version of the dll, you can now deploy this to the GAC either using gacutil /i Yourdll2.dll or by just dragging the dll file to c:\windows\assembly.
Wednesday, 15 February 2012
Host Workflows stored in database as WCF services in ASP.Net MVC
Today I am going to show you how to host WF4 workflows in WCF which are stored in database. We'll be using ASP.Net MVC as the solution project.
On internet, there are several examples available on how to host compiled workflows as WCF service but my requirement was to get the workflow from database and expose as WCF end points.
After a lot of research and hacking, I was able to create a small sample project that does the work. My entire code goes in global.asax.cs file.
When you open global.asax.cs file and find an entry where mvc register the routes to the Routecollection. This routing helps IIS to redirect the incoming request with the route specified. As I already told my workflows are stored in database in table Workflows so now I'm going to get them from database and add to Routecollection as ServiceRoute.
foreach(var wf in GetAllWorkflows())
{
routes.Add(new ServiceRoute(wf.Name, new TestWorkflowServiceHostFactory(wf.Name), typeof(Workflow1)));
}
GetAllWorkflows() is the method which returns a list of workflows from database. I store them as a list of custom class of workflows. I'm not explaining here how to get the data from database for the sake of simplicity but you can use ADO.Net classes or Ado.Net Entity data model or some other mode.
Here are some interesting bits to understand about the code. The ServiceRoute class enables the creation of service routes over Http for WCF services with support for extension-less base addresses. What does it mean. It means when an incoming request is received by IIS, it route the request using this Url. And how does it do that...Well, when the ServiceRoute constructor is called, it adds the corresponding route prefix (url pattern) and a (hidden) route handler to the ASP.NET RouteCollection, and cache the corresponding route prefix, service host factory and service type info into an internal hash table for future service activation. The three arguments used here are defined below: -
Now we come to the implementation of WorkflowServiceHostFactory. TestWorkflowServiceHostFactory is inherited by WorkflowServiceHostFactory class. This class declare two constants to set/get temp files to stores the workflow/'workflow service' definitions.
private const string TempXamlFile = "temp.xaml";
private const string TempXamlService = "temp.xamlx";
The constructor holds the name of workflow passed at the time of registering routes.
public TriboldWorkflowServiceHostFactory(string routePrefix)
{
XamlRoutePrefix = routePrefix;
...
}
Override CreateServiceHost method to create the host and return to IIS. This is the main method that does the magic.
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
...
Activity activity1 = LoadActivity(XamlRoutePrefix);
var host = new WorkflowServiceHost(activity1, baseAddresses);
...
}
public static void RegisterRoutes(RouteCollection routes)
{
foreach(var wf in GetAllWorkflows())
{
routes.Add(new ServiceRoute(wf.Name, new TestWorkflowServiceHostFactory(wf.Name), typeof(Workflow1)));
}
}
On internet, there are several examples available on how to host compiled workflows as WCF service but my requirement was to get the workflow from database and expose as WCF end points.
After a lot of research and hacking, I was able to create a small sample project that does the work. My entire code goes in global.asax.cs file.
When you open global.asax.cs file and find an entry where mvc register the routes to the Routecollection. This routing helps IIS to redirect the incoming request with the route specified. As I already told my workflows are stored in database in table Workflows so now I'm going to get them from database and add to Routecollection as ServiceRoute.
foreach(var wf in GetAllWorkflows())
{
routes.Add(new ServiceRoute(wf.Name, new TestWorkflowServiceHostFactory(wf.Name), typeof(Workflow1)));
}
GetAllWorkflows() is the method which returns a list of workflows from database. I store them as a list of custom class of workflows. I'm not explaining here how to get the data from database for the sake of simplicity but you can use ADO.Net classes or Ado.Net Entity data model or some other mode.
Here are some interesting bits to understand about the code. The ServiceRoute class enables the creation of service routes over Http for WCF services with support for extension-less base addresses. What does it mean. It means when an incoming request is received by IIS, it route the request using this Url. And how does it do that...Well, when the ServiceRoute constructor is called, it adds the corresponding route prefix (url pattern) and a (hidden) route handler to the ASP.NET RouteCollection, and cache the corresponding route prefix, service host factory and service type info into an internal hash table for future service activation. The three arguments used here are defined below: -
- RoutePrefix -> WCF Endpoint will be exposed with this name.
- TestWorkflowServiceHostFactory -> Inherited from WorkflowServiceHostFactory. Factory that provides instances of WorkflowServiceHost in managed hosting environments (e.g. IIS) where the host instance is created dynamically in response to incoming messages. It means a serviceHostFactory that is used by IIS to create a new instance of service host everytime a new request is received. This class accepts workflow name as string which is the name of workflow stored in database and is further used to expose the endpoints. (e.g. If the workflow name is GetMyName then the endpoints exposed would be something like http://localhost:1997/GetMyName)
- ServiceType -> Used to get the type of service. Since we are creating workflows from database so we can't have types for workflows. So what do we pass here? Now this is a bit hacky. I added a workflow.xaml class in project and refer it here (I tried passing with typeof(object) but it didn't work somehow...however didn't get enough time to explore the reason).
Now we come to the implementation of WorkflowServiceHostFactory. TestWorkflowServiceHostFactory is inherited by WorkflowServiceHostFactory class. This class declare two constants to set/get temp files to stores the workflow/'workflow service' definitions.
private const string TempXamlFile = "temp.xaml";
private const string TempXamlService = "temp.xamlx";
The constructor holds the name of workflow passed at the time of registering routes.
public TriboldWorkflowServiceHostFactory(string routePrefix)
{
XamlRoutePrefix = routePrefix;
...
}
Override CreateServiceHost method to create the host and return to IIS. This is the main method that does the magic.
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
...
//WorkflowService service = LoadWFService(XamlRoutePrefix);
//var host = new WorkflowServiceHost(service, baseAddresses);
Activity activity1 = LoadActivity(XamlRoutePrefix);
var host = new WorkflowServiceHost(activity1, baseAddresses);
...
host.Description.Behaviors.Add(new AspNetCompatibilityRequirementsAttribute() { RequirementsMode = AspNetCompatibilityRequirementsMode.Required });
host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
return host;
}
The LoadActivity method get the workflowDefinition from DB and create a temp.xaml file. Create the file stream and writes the workflow definition into the stream. (I was getting 'File in use' error while loading xaml file from stream so I closed the stream and again open in read mode to load the .xaml file) ActivityXamlServices.Load method uses this stream to load and return Activity object
...
var stream = new FileStream(Path.Combine(tempPath, TempXamlFile), FileMode.Create);
byte[] bytes = Encoding.ASCII.GetBytes(workflowDefinition);
stream.Write(bytes, 0, Encoding.ASCII.GetByteCount(workflowDefinition));
stream.Flush();
stream.Close();
stream = File.OpenRead(Path.Combine(tempPath, TempXamlFile));
var service = ActivityXamlServices.Load(stream);
stream.Flush();
stream.Close();
return service as Activity;
Now, we've got the activity, so we use WorkflowServiceHost class and pass this activity with baseAddress thus creating the host and return to the IIS.
And that's all, We've done the job. In the same manner we can load Workflow service. The only difference is we need to pass workflowservice object in WorkflowServiceHost as mentioned above (commented code)
Here's the complete code...
public class TestWorkflowServiceHostFactory : WorkflowServiceHostFactory
{
private const string TempXamlFile = "temp.xaml";
private const string TempXamlService = "temp.xamlx";
public string XamlRoutePrefix { get; set; }
public TriboldWorkflowServiceHostFactory(string routePrefix)
{
XamlRoutePrefix = routePrefix;
}
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
var type = GetObjectOrThrowNotFound(() => Type.GetType(constructorString));
var activity = GetObjectOrThrowNotFound(() => Activator.CreateInstance(type) as Activity);
Activity activity1 = LoadActivity(XamlRoutePrefix);
var host = new WorkflowServiceHost(activity1, baseAddresses);
//WorkflowService service = LoadWFService(XamlRoutePrefix);
//var host = new WorkflowServiceHost(service, baseAddresses);
host.Description.Behaviors.Add(new AspNetCompatibilityRequirementsAttribute() { RequirementsMode = AspNetCompatibilityRequirementsMode.Required });
host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
return host;
}
private Activity LoadActivity(string searchString)
{
var input = new Dictionary<string, object>();
input["@WorkflowName"] = searchString;
DataTable dt = MvcApplication.FetchData.Fetch(input, "GetWorkflowList");
string workflowDefinition = dt.Rows[0][1].ToString();
string tempPath = Path.GetTempPath();
//if file already exists then delete it
if (File.Exists(Path.Combine(tempPath, TempXamlFile)))
File.Delete(Path.Combine(tempPath, TempXamlFile));
var stream = new FileStream(Path.Combine(tempPath, TempXamlFile), FileMode.Create);
byte[] bytes = Encoding.ASCII.GetBytes(workflowDefinition);
stream.Write(bytes, 0, Encoding.ASCII.GetByteCount(workflowDefinition));
stream.Flush();
stream.Close();
stream = File.OpenRead(Path.Combine(tempPath, TempXamlFile));
var service = ActivityXamlServices.Load(stream);
stream.Flush();
stream.Close();
return service as Activity;
}
private WorkflowService LoadWFService(string searchString)
{
var input = new Dictionary<string, object>();
input["@WorkflowName"] = searchString;
DataTable dt = MvcApplication.FetchData.Fetch(input, "GetWorkflowList");
string workflowDefinition = dt.Rows[0][1].ToString();
string tempPath = Path.GetTempPath();
//if file already exists then delete it
if (File.Exists(Path.Combine(tempPath, TempXamlService)))
File.Delete(Path.Combine(tempPath, TempXamlService));
var stream = new FileStream(Path.Combine(tempPath, TempXamlService), FileMode.Create);
byte[] bytes = Encoding.ASCII.GetBytes(workflowDefinition);
stream.Write(bytes, 0, Encoding.ASCII.GetByteCount(workflowDefinition));
stream.Flush();
stream.Close();
stream = File.OpenRead(Path.Combine(tempPath, TempXamlService));
var service = XamlServices.Load(stream);
stream.Flush();
stream.Close();
return service as WorkflowService;
}
public static void RegisterRoutes(RouteCollection routes)
{
foreach(var wf in GetAllWorkflows())
{
routes.Add(new ServiceRoute(wf.Name, new TestWorkflowServiceHostFactory(wf.Name), typeof(Workflow1)));
}
}
Thursday, 23 June 2011
Could not load file or assembly XX. System.BadImageFormatException
I got this error when I was working with Silverlight RIA application which works on MVVM pattern. Everything was running smoothly before when I was using VSTS2010 internal web server. As soon as I host my asp.net application which host silverlight application to IIS 7 of windows 7 I got the above error.
Although I was using dll which was built on any CPU platform. After some digging I found out that there is a setting in IIS 7 in Application Pool that you can set.
Open IIS using InetMgr command, locate & click your web application. In the action tab select Advanced Settings and view your application pool.
Now goto application pool and locate the pool viewed in last step. Click your pool and select Advanced Settings from action tab. In General settings Enable 32-Bit Applications set this value to true.
After setting this when I tried to run the application, I got "Login failed for user 'IIS APPPOOL\ASP.NET v4.0'" error. You may overcome this by going to Process Model in Application pool and in Identity setting, change ApplicationPoolIdentity to NetworkService or LocalSystem and you're go.
Make sure to remove anonymous access and add windows authentication (if using windows auth) to your asp.net web application.
Although I was using dll which was built on any CPU platform. After some digging I found out that there is a setting in IIS 7 in Application Pool that you can set.
Open IIS using InetMgr command, locate & click your web application. In the action tab select Advanced Settings and view your application pool.
Now goto application pool and locate the pool viewed in last step. Click your pool and select Advanced Settings from action tab. In General settings Enable 32-Bit Applications set this value to true.
After setting this when I tried to run the application, I got "Login failed for user 'IIS APPPOOL\ASP.NET v4.0'" error. You may overcome this by going to Process Model in Application pool and in Identity setting, change ApplicationPoolIdentity to NetworkService or LocalSystem and you're go.
Make sure to remove anonymous access and add windows authentication (if using windows auth) to your asp.net web application.
Thursday, 19 May 2011
Windows App : Set an image in ListViewSubItem
For background details about the error, see this link...
http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/229d8b6e-f167-48ee-aa2e-931e6ee7eb2b/
If you really do wanna set an image in ListVewSubItem, it's a bit tricky. You've to handle the following events of a list view
DrawSubItem
DrawColumnHeader
Now implement DrawColumnHeader
private void ListViewDrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}
http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/229d8b6e-f167-48ee-aa2e-931e6ee7eb2b/
If you really do wanna set an image in ListVewSubItem, it's a bit tricky. You've to handle the following events of a list view
DrawSubItem
DrawColumnHeader
Now implement DrawColumnHeader
private void ListViewDrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}
And DrawSubItem
private void ListViewDrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
if (e.ColumnIndex == 1)
{
((TriboldExtendedListView) sender).FullRowSelect = true;
if (e.ColumnIndex == 1)
{
//This allows window to draw background otherwise your column would be blank in //anyway
e.DrawBackground();
//Draw you graphics here
e.Graphics.DrawImage(img, new System.Drawing.RectangleF(e.Item.Position.X + Columns[0].Width - 10, e.Item.Position.Y, img.Width*(fScale), img.Height*(fScale)));
//Draw your text here
e.DrawText(TextFormatFlags.Left);
//This is something really important if you want to set the highlighted color //(windows blue) to the ListViewSubItem
e.Item.UseItemStyleForSubItems = false;
if (e.Item.Selected)
{
e.Item.SubItems[1].BackColor = System.Drawing.Color.FromArgb(51, 153, 255);
e.Item.SubItems[1].ForeColor = System.Drawing.Color.FromArgb(255, 255, 255);
}
else
{
e.Item.SubItems[1].BackColor = System.Drawing.Color.FromArgb(229, 229, 229);
e.Item.SubItems[1].ForeColor = System.Drawing.Color.FromArgb(0, 0, 0);
}
}}
{
//This allows window to draw background otherwise your column would be blank in //anyway
e.DrawBackground();
//Draw you graphics here
e.Graphics.DrawImage(img, new System.Drawing.RectangleF(e.Item.Position.X + Columns[0].Width - 10, e.Item.Position.Y, img.Width*(fScale), img.Height*(fScale)));
//Draw your text here
e.DrawText(TextFormatFlags.Left);
//This is something really important if you want to set the highlighted color //(windows blue) to the ListViewSubItem
e.Item.UseItemStyleForSubItems = false;
if (e.Item.Selected)
{
e.Item.SubItems[1].BackColor = System.Drawing.Color.FromArgb(51, 153, 255);
e.Item.SubItems[1].ForeColor = System.Drawing.Color.FromArgb(255, 255, 255);
}
else
{
e.Item.SubItems[1].BackColor = System.Drawing.Color.FromArgb(229, 229, 229);
e.Item.SubItems[1].ForeColor = System.Drawing.Color.FromArgb(0, 0, 0);
}
}}
}
If you need any information about, feel free to post.
Subscribe to:
Posts (Atom)