I want to generate Web API 2 controller dynamically on startup. In this case, I used reflection.
However, when I compiled it and started the service, I got exception when I call it via a HttpRequest. The exception looks like this:
{
"Message": "An error has occurred.",
"ExceptionMessage": "Unable to cast object of type 'System.Object[]' to type 'System.Web.Http.ParameterBindingAttribute[]'.",
"ExceptionType": "System.InvalidCastException",
"StackTrace": " at System.Web.Http.Controllers.ReflectedHttpParameterDescriptor.GetCustomAttributes[TAttribute]() at System.Web.Http.Controllers.HttpParameterDescriptor.FindParameterBindingAttribute() at System.Web.Http.Controllers.HttpParameterDescriptor.get_ParameterBinderAttribute() at System.Web.Http.ModelBinding.DefaultActionValueBinder.GetParameterBinding(HttpParameterDescriptor parameter) at System.Array.ConvertAll[TInput,TOutput](TInput[] array, Converter`2 converter) at System.Web.Http.ModelBinding.DefaultActionValueBinder.GetBinding(HttpActionDescriptor actionDescriptor) at System.Web.Http.Controllers.HttpActionDescriptor.get_ActionBinding() at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem..ctor(HttpControllerDescriptor controllerDescriptor) at System.Web.Http.Controllers.ApiControllerActionSelector.GetInternalSelector(HttpControllerDescriptor controllerDescriptor) at System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext) at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken) at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"
}
Here is my code set.
// WebApiConfig.cs
// Register function in WebApiConfig
public static void Register(HttpConfiguration config)
{
// Replace services by this line, to add my custom HttpControllerSelector
config.Services.Replace(typeof(IHttpControllerSelector), new CustomHttpControllerSelector(config));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
// CustomeHttpControllerSelector.cs
// CustomHttpControllerSelector class to override the default one (In this case, the same controller will be returned for all request)
public class CustomHttpControllerSelector : DefaultHttpControllerSelector
{
private readonly HttpConfiguration _configuration;
public CustomHttpControllerSelector(HttpConfiguration configuration) : base(configuration)
{
_configuration = configuration;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var ctrl = ControllerBuilder.CreateNewObject();
String controllerName = base.GetControllerName(request);
return new HttpControllerDescriptor(_configuration, controllerName, ctrl.GetType());
}
}
// ControllerBuilder.cs
// The builder to implement my custom controller. Seems the problem is here. Maybe I configured something wrong in this section?
public static class ControllerBuilder
{
public static ApiController CreateNewObject()
{
var ctrlType = CompileResultType("TmpController", new Dictionary<string, string> { { "tmpField", "System.String" } });
var ctrlObject = Activator.CreateInstance(ctrlType);
return ctrlObject as ApiController;
}
public static Type CompileResultType(string typeSignature, Dictionary<string, string> propDic)
{
TypeBuilder tb = GetTypeBuilder(typeSignature);
tb.SetParent(typeof(ApiController));
ConstructorBuilder ctor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
foreach (var item in propDic)
{
CreateProperty(tb, item.Key, Type.GetType(item.Value));
}
// For this controller, I only want a Get method to server Get request
MethodBuilder myGetMethod =
tb.DefineMethod("Get",
MethodAttributes.Public,
typeof(String), new Type[] { typeof(String) });
// Generate IL for method.
ILGenerator myMethodIL = myGetMethod.GetILGenerator();
myMethodIL.Emit(OpCodes.Ldarg_1);
myMethodIL.Emit(OpCodes.Ret);
Type objectType = tb.CreateType();
return objectType;
}
private static TypeBuilder GetTypeBuilder(string typeSignature)
{
var an = new AssemblyName(typeSignature);
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
null);
return tb;
}
private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
{
FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
ILGenerator getIl = getPropMthdBldr.GetILGenerator();
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, fieldBuilder);
getIl.Emit(OpCodes.Ret);
MethodBuilder setPropMthdBldr =
tb.DefineMethod("set_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
null, new[] { propertyType });
ILGenerator setIl = setPropMthdBldr.GetILGenerator();
Label modifyProperty = setIl.DefineLabel();
Label exitSet = setIl.DefineLabel();
setIl.MarkLabel(modifyProperty);
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, fieldBuilder);
setIl.Emit(OpCodes.Nop);
setIl.MarkLabel(exitSet);
setIl.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropMthdBldr);
propertyBuilder.SetSetMethod(setPropMthdBldr);
}
}
I have tried to Invoke "Get" method of ctrlObject in CreateNewObject() by ctrlType.GetMethod("Get").Invoke(ctrlObject, new [] {"param1"})
. However, I can get the return string "param1"!!!. So, may I know what problem is causing the exception that I quoted at the top while I request it as a real Web API controller?
Aucun commentaire:
Enregistrer un commentaire