Krunal Modi
Krunal Modi

Reputation: 155

BindModel gets executed before ActionFilterAttribute

I have started running into a weird problem.

I have ASP.NET project in which I have an API which takes POST params. Since I use an interface, I used custom deserializer to read POST object. It worked properly till last few days. But, one day I started getting 500 - Internal server error saying "Cannot create an instance of an interface. ... through CreateModel". At that time, I was using PostMan app. As there was virtually no change in the code, I thought may be PostMan App got corrupted.

I wasn't sure, so I had tried same query on Fiddler and it worked fine. Now, after 3-4 days, Fiddler also stopped working with same error.

After digging through, I found that somehow 'BindModel' has started executing may be before ActionFilterAttribute. I'm just not sure how is it possible. Is there any workaround to overcome this situation? My post http call is just not entering JsonFilter's OnActionExecuting method

Error msg:

[MissingMethodException: Cannot create an instance of an interface.]
System.RuntimeTypeHandle.CreateInstance(...)
System.RuntimeType.CreateInstanceSlow(...)
System.Activator.CreateInstance(...)
System.Activator.CreateInstance(...)
System.Web.Mvc.DefaultModelBinder.CreateModel(...)

[MissingMethodException: Cannot create an instance of an interface. Object type 'MyCommonObj.IMyInterface'.]
System.Web.Mvc.DefaultModelBinder.CreateModel(...)
System.Web.Mvc.DefaultModelBinder.BindComplexModel(...)
System.Web.Mvc.DefaultModelBinder.BindModel(...)

Code Snippet:

public class JsonFilter : ActionFilterAttribute {
    public string Parameter { get; set; }
    public Type JsonDataType { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext) {
        if (filterContext.HttpContext.Request.ContentType.Contains("application/json")) {
            string inputContent;
            filterContext.HttpContext.Request.InputStream.Position = 0;
            using (var sr = new StreamReader(filterContext.HttpContext.Request.InputStream)) {
                inputContent = sr.ReadToEnd();
            };

            var jsonSerializerSettings = new JsonSerializerSettings() {
                TypeNameHandling = TypeNameHandling.All
            };

            if (JsonDataType == typeof(MyClass)) {
                var result = JsonConvert.DeserializeObject<MyClass>(inputContent, jsonSerializerSettings);
                filterContext.ActionParameters[Parameter] = result;
            }
            else {
                throw new NotImplementedException();
            }
        }
    }
}

[HttpPost]
[JsonFilter(Parameter = "config", JsonDataType = typeof(MyClass))]
public ActionResult ExecuteApi(MyClass config) {
    var result = DoSomething(config);
    return Json(result);
}

public interface IMyInterface {
    string GetValue();
}

public class MyDerivedClass : IMyInterface {
    public string Value { get; set; }

    public MyDerivedClass(string v) {
        Value = v;
    }

    public string GetValue() { return Value; }
}

public class Query {
    [JsonProperty(TypeNameHandling = TypeNameHandling.All)] 
    public IMyInterface type { get; set; }

    public Query () {}
}

public class MyClass {
    List<Query> myList { get; set; }

    public MyClass () {}
}

Upvotes: 0

Views: 1552

Answers (1)

NightOwl888
NightOwl888

Reputation: 56859

Action filters always run after the model binder. If you need a filter to run before the model binder, you should use IAuthorizationFilter.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class JsonFilter : Attribute, IAuthorizationFilter
{
    public string Parameter { get; set; }
    public Type JsonDataType { get; set; }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.ContentType.Contains("application/json"))
        {
            string inputContent;
            filterContext.HttpContext.Request.InputStream.Position = 0;
            using (var sr = new StreamReader(filterContext.HttpContext.Request.InputStream))
            {
                inputContent = sr.ReadToEnd();
            };

            var jsonSerializerSettings = new JsonSerializerSettings()
            {
                TypeNameHandling = TypeNameHandling.All
            };

            if (JsonDataType == typeof(MyClass))
            {
                var result = JsonConvert.DeserializeObject<MyClass>(inputContent, jsonSerializerSettings);
                filterContext.ActionParameters[Parameter] = result;
            }
            else
            {
                throw new NotImplementedException();
            }
        }
    }
}

Upvotes: 1

Related Questions