New in 5.5: Provide your classes from App_Code

   —   
5.5 is out for some time and you sure deserve to know what is new. You sure know about the standard features you can see, but there are also some API improvements or things that haven't yet been in the Beta version. So here is the first one of them ...
Hi there,

There are certain tasks, that require you to do more than you actually want for quite a simple actions. One of them is defining your custom Scheduled task.

The scheduled task requires you to provide the assembly name and class name so it can load the class dynamically and execute it. This clearly results in you having to open Visual studio, add new class library, and implement the class in there, taking care about the references, and recompilation of the project if a hotfix is applied.

It is kind of too complicated if you ask me. That is why 5.5 offers better, alternative solution to it.

Providing class from App_Code

As the title indicates, in 5.5 there is a way how you can provide your custom classes (of any kind) from the App_Code folder. There isn't any DLL you could refer to for the App_Code of your web site project, but now there is a handler to take care of this. Scheduled task configuration then looks like this:

Task assembly name: App_Code (case sensitive)
Task class name: Custom.MyTask (any selector that you handle and provide the task class)



Now when we have the task defined, we can look how this is handled in the App_Code so you can provide the class object.

Open the file ~/App_Code/Global/CMS/CMSCustom.cs, you can see this code in there (I removed comments to make it shorter):

public static object GetCustomClass(string className)
{
  switch (className) 
  {
    case "Custom.MyTask":
      return new MyTask();
  }

  return null;
}

public class MyTask : ITask
{
  public string Execute(TaskInfo ti)
  {
    EventLogProvider ev = new EventLogProvider();
    ev.LogEvent(EventLogProvider.EVENT_TYPE_INFORMATION, DateTime.Now, "MyTask", "Execute", null, "This task was executed from '~/App_Code/Global/CMS/CMSCustom.cs'.");

    return null;
  }
}


As you can see, there is a handler that you can implement to provide the custom objects. It is basically called with the class name you enter to the task which in our case is "Custom.MyTask". And since you know this targets the scheduled task, you provide a new object with the ITask interface just as you do in your DLL.

This particular sample task just logs it's execution to the Kentico CMS event log so you can see that it really works. Yours can do just anything.

Where can it be used?

You can use this anywhere in the system where you provide the assembly name (such as Notification/Payment gateways, etc.), if you put in App_Code, it will just ask for the class through the handler instead of loading the DLL.

Advantages of this approach

The advantages are pretty clear, but just to summarize:
  • No need to recompile your DLLs after applying hotfix or upgrade
  • No need to recompile any DLL if you change the task code
  • Able to provide different task handlers based on the context of execution (e.g. for each web farm server / site / etc.)
And that is pretty much all for today, just as in previous post, this will also be used in our simple Chat module I will elaborate on later.

Enjoy!
Share this article on   LinkedIn Google+

Martin Hejtmanek

Hi, I am the CTO of Kentico and I will be constantly providing you the information about current development process and other interesting technical things you might want to know about Kentico.

Comments

Martin Hejtmanek commented on

Hi,

The <add key="CMSCustomEcommerceProviderAssembly" value="App_Code" /> tag is not neccesary in this case, it is related to something else.

Does your switch in GetCustomClass have case for "App_Code.CustomProvider.CustomGateway"? Could you please post the GetCustomClass method code?

vlad-inorbital commented on

Hi Martin,

I am trying to incorporate the example in
http://devnet.kentico.com/docs/5_5r2/ecommerceguide/index.html?developing_custom_payment_gateways.htm

but I receive this error "[SchedulingExecutor.ExecuteTask]: Cannot load provider class 'CMS.CustomProvider.CustomGateway' from assembly 'App_Code'."

here were my steps, i created the CustomGateway.cs, moved it to the App_Code/Global/ folder.

I added a task with these parameters.

Task display name: CustomGateway
Task name: CustomGateway
Task assembly name: App_Code
Task class name: App_Code.CustomProvider.CustomGateway

and enabled it.
I tried adding the <add key="CMSCustomEcommerceProviderAssembly" value="App_Code" /> tag to webconfig.

I'm sure I messed something up along the way, but I'm not sure how to continue.

Martin Hejtmanek commented on

Hi John,

Just use the way of combining CS and VB in one project, you basically have two options then:

1) Rewrite the CMSCustom to VB and place it to VB folder

2) Keep the current CMSCustom, just implement the switch in CS, and implement the rest in VB in other class

I will make a blog post about it later if I have enough time ...

John commented on

Hi Martin,
Could you please do me a huge favor and give an example of this in VB.NET?

John commented on

I forgot <add key="CMSUseCustomHandlers" value="true"/>

NOW IT WORKS!

Thanks

Martin Hejtmanek commented on

Did you also enable the custom handlers with this?

<add key="CMSUseCustomHandlers" value="true"/>

John commented on

Hi,

thanks for your efforts to help me.

In Web.config in AppSettings:
<add key="CMSCustomHandlersAssembly" value="App_Code"/>

My code in CMSCustom.cs (I left out using):
public static class CMSCustom
{
public static void Init()
{
MacroResolver.OnResolveCustomMacro += new MacroResolver.MacroHandler(ResolveCustomMacro);
ClassHelper.OnGetCustomClass += new ClassHelper.GetClassEventHandler(GetCustomClass);
}




/// <summary>
/// Custom macro handler
/// </summary>
/// <param name="sender">Sender (active macro resolver)</param>
/// <param name="expression">Expression to resolve</param>
/// <param name="match">Returns true if the macro matches (was resolved)</param>
public static string ResolveCustomMacro(MacroResolver sender, string expression, out bool match)
{
match = false;
string result = expression;

// Add your custom macro evaluation
/*
switch (expression.ToLower())
{
case "someexpression":
match = true;
result = "Resolved expression";
break;
}
*/

return result;
}

//This never get called (tested using break point)
public static object GetCustomClass(string className)
{
switch (className)
{
you can provide your scheduled tasks out of App_Code
case "Custom.MyTask":
return new MyTask();
case "App_Code.CustomDataHandler":
return new CustomDataHandler();
}
return null;
}

public class CustomDataHandler : AbstractDataHandler
{...

Thanks,

John

Martin Hejtmanek commented on

Yes, put in just "App_Code", the other part is added by the system. If you already do that, send me the exact information about what you did.

John commented on

I tried it with App_Code.CustomDataHandler but GetCustomClass is never called on data insertion!

public static void Init()
{
MacroResolver.OnResolveCustomMacro += new MacroResolver.MacroHandler(ResolveCustomMacro);
ClassHelper.OnGetCustomClass += new ClassHelper.GetClassEventHandler(GetCustomClass);
}

Any ideas?

Martin Hejtmanek commented on

Hi Patrick,

That would be the same as with the handlers, you add the key:

<add key="CMSCustomEcommerceProviderAssembly" value="App_Code" />

And implement all classes such as "App_Code.CustomOrderInfoProvider" in the App_Code.

However, I do not recommend to do that just yet, because 6.0 will provide much shoother way of e-commerce customization that will also be possible to do from App_Code.

Patrick D'Souza commented on

Can you comment on using a similar approach for custom ecommerce providers?

http://devnet.kentico.com/docs/ecommerceguide/index.html?using_custom_providers.htm

replica watches commented on

It is a very nice stuff you are sharing us! Thanks for it !

Jeroen Fürst commented on

Oh haha ofcourse, that did the trick :) Thanks Martin!!!

Martin Hejtmanek commented on

Hi Jeroen,

Sure, as I said, in such case you need to implement them all in there. In case there is no option to insert specific class name, the class name is joined with the assembly name, so in this case it is "App_Code.CustomSecurityHandler". Your implementation should look like this:

public static object GetCustomClass(string className)
{
switch (className)
{
case "App_Code.CustomDataHandler":
return new CustomDataHandler();

case "App_Code.CustomSecurityHandler":
return new CustomSecurityHandler();

...
}
return null;
}


public class CustomSecurityHandler : AbstractSecurityHandler
{
public override object OnAuthentication(object userInfo, string username, string password)
{
return userInfo;
}

...
}

I tried that and it is working like a charm.

Jeroen Fürst commented on

Hmmm getting a lot of: The class CustomSecurityHandler couldn't be loaded exception's when moving the classes to App_Code... Any thoughts?

Martin Hejtmanek commented on

Hi John, I think this should be able to cover this too since all the code that loads external classes is handled centrally, try to adding this to your web.config:

<add key="CMSCustomHandlersAssembly" value="App_Code"/>

Check if the GetCustomClass if called, and let us know if it worked. Note that in this case you should provide all the classes available in the current library.

To be honest, the overall customization using the entire compiled libraries must be seriously revamped, because if you want to override just one method, it just gets too complicated. It is part of our plans already.

John commented on

We would like to see the same approach for custom event handling!

Thanks

Ivan Robalino commented on

Thanks Martin. I've been waiting this for a long time. Our implementation has several custom scheduled tasks and it was cumbersome to apply any hotfix since I had to recompile the DLLs, etc.

Martin Hejtmanek commented on

Great to hear. If there are any other things you are looking for time to time, definitely let me know, they may already be there or can be done occasionally in new versions.

Jeroen Fürst commented on

Thanks Martin, exactly what we are looking for!