Mixing Custom and Default Model Binding - asp.net

I need to run some code to further databind some model after the default model binding is done. I don't want to completely replace the existing model binding.
This question explains how this is done in pre-CORE ASP.NET:
ASP.NET MVC - Mixing Custom and Default Model Binding
However that approach doesn't seem to work in ASP.NET Core because there is no DefaultModelBinder class any more.
What alternative can be used in ASP.NET Core?

You can leverage the ComplexTypeModelBinder to do the actual work, then inject your own logic after it is done.
For example (assuming your custom type is MyCustomType):
public class MyCustomType
{
public string Foo { get; set; }
}
public class MyCustomTypeModelBinder : IModelBinder
{
private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;
public MyCustomTypeModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders)
{
this._propertyBinders = propertyBinders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var complexTypeModelBinder = new ComplexTypeModelBinder(this._propertyBinders);
// call complexTypeModelBinder
await complexTypeModelBinder.BindModelAsync(bindingContext);
var modelBound = bindingContext.Model as MyCustomType;
// do your own magic here
modelBound.Foo = "custominjected";
}
}
public class MyCustomTypeModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(MyCustomType))
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++)
{
var property = context.Metadata.Properties[i];
propertyBinders.Add(property, context.CreateBinder(property));
}
return new MyCustomTypeModelBinder(propertyBinders);
}
return null;
}
}
Then register it:
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new MyCustomTypeModelBinderProvider());
});

Related

.NET ApiController: Custom model binding behaviour using [FromBody] T

I use following code to deserialize an instance of MyModel
public async Task<IHttpActionResult> DoSomething([FromBody] MyModel model)
class MyModel {
string a;
int b;
}
Is there away to enforce stricter model binding so that following input won't work?
{
"a":"someString",
"b": 4,
"c": "somethingIWantToCauseAnErrorWhenPresent"
}
You can write your own ActionFilter to lookup the request body and basing on your logic throw an exception
public class CustomValidationFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
var bodyLength = context.HttpContext.Request.Body.Length;
var buffer = new byte[bodyLength];
context.HttpContext.Request.EnableRewind();
context.HttpContext.Request.Body.Position = 0;
var streamReader = new StreamReader(context.HttpContext.Request.Body);//do not dispose this streamReader
var requestBody = streamReader.ReadToEnd();
var jsonBody = JsonConvert.DeserializeObject<JObject>(requestBody);
if (jsonBody.Property("c") != null) // your custom validation
{
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
Either you apply this filter globaly, in whole API like this (.net core 2.2):
services.AddMvc(options => options.Filters.Add(typeof(CustomValidationFilter))).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
or inherit from Attribute class and put it on certain action/controller

Combine [FromBody] with [FromHeader] in WebAPI in .net Core 3.0

we are writing some API which required sessionId in header and some other data in body.
Is it possible to have only one class automatically parsed partially from header and from body?
Something like:
[HttpGet("messages")]
[Produces("application/json")]
[Consumes("application/json")]
[Authorize(Policy = nameof(SessionHeaderKeyHandler))]
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(PendingMessagesData pendingMessagesRequest)
{
some body...
}
with request class like:
public class PendingMessagesData
{
[FromHeader]
public string SessionId { get; set; }
[FromBody]
public string OrderBy { get; set; }
}
I know, it is possible to do this, but it means, that I have to pass SessionId into the other methods as a parameter, instead of pass only one object. And we would have to do that in every API call.
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(
[FromHeader] string sessionId,
[FromBody] PendingMessagesData pendingMessagesRequest)
{
some body...
}
Thank you,
Jakub
we are writing some API which required sessionId in header and some other data in body. Is it possible to have only one class automatically parsed partially from header and from body
Your GetPendingClockInMessages is annotated with a [HttpGet("messages")]. However, a HTTP GET method has no body at all. Also, it can't consume application/json. Please change it to HttpPost("messages")
Typically, SessionId is not passed in header of Session: {SessionId} like other HTTP headers. Session are encrypted via IDataProtector. In other words, you can't get it by Request.Headers["SessionId"].
Apart from the above two facts, you can create a custom model binder to do that.
Since the Session doesn't come from header directly, let's create a custom [FromSession] attribute to replace your [FromHeader]
public class FromSessionAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromSession", "FromSession Binding Source", true, true);
public BindingSource BindingSource { get { return FromSessionAttribute.Instance; } }
}
And since you're consuming application/json, let's create a binder as below:
public class MyModelBinder : IModelBinder
{
private readonly JsonOptions jsonOptions;
public MyModelBinder(IOptions<JsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions.Value;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var type = bindingContext.ModelType;
var pis = type.GetProperties();
var result= Activator.CreateInstance(type);
var body= bindingContext.ActionContext.HttpContext.Request.Body;
var stream = new System.IO.StreamReader(body);
var json = await stream.ReadToEndAsync();
try{
result = JsonSerializer.Deserialize(json, type, this.jsonOptions.JsonSerializerOptions);
} catch(Exception){
// in case we want to pass string directly. if you don't need this feature, remove this branch
if(pis.Count()==2){
var prop = pis
.Where(pi => pi.PropertyType == typeof(string) )
.Where(pi => !pi.GetCustomAttributesData().Any(ca => ca.AttributeType == typeof(FromSessionAttribute)))
.FirstOrDefault();
if(prop != null){
prop.SetValue( result ,json.Trim('"'));
}
} else{
bindingContext.ModelState.AddModelError("", $"cannot deserialize from body");
return;
}
}
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError("sessionId", $"cannot get SessionId From Session");
return;
} else {
var props = pis.Where(pi => {
var attributes = pi.GetCustomAttributesData();
return attributes.Any( ca => ca.AttributeType == typeof(FromSessionAttribute));
});
foreach(var prop in props) {
prop.SetValue(result, sessionId);
}
bindingContext.Result = ModelBindingResult.Success(result);
}
}
}
How to use
Decorate the property with a FromSession to indicate that we want to get the property via HttpContext.Sessino.Id:
public class PendingMessagesData
{
[FromBody]
public string OrderBy { get; set; } // or a complex model: `public MySub Sub{ get; set; }`
[FromSession]
public string SessionId { get; set; }
}
Finally, add a modelbinder on the action method parameter:
[HttpPost("messages")]
[Produces("application/json")]
[Consumes("application/json")]
public async Task<ActionResult> GetPendingClockInMessages([ModelBinder(typeof(MyModelBinder))]PendingMessagesData pendingMessagesRequest)
{
return Json(pendingMessagesRequest);
}
Personally, I would prefer another way, i.e, creating a FromSessionBinderProvider so that I can implement this without too much effort. :
public class FromSessionDataModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError(sessionId, $"cannot get SessionId From Session");
} else {
bindingContext.Result = ModelBindingResult.Success(sessionId);
}
return Task.CompletedTask;
}
}
public class FromSessionBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var hasFromSessionAttribute = context.BindingInfo?.BindingSource == FromSessionAttribute.Instance;
return hasFromSessionAttribute ?
new BinderTypeModelBinder(typeof(FromSessionDataModelBinder)) :
null;
}
}
(if you're able to remove the [ApiController] attribute, this way is more easier).

How do I test an ActionFilterAttribute with ASP.NET Web API?

I'm building an ASP.NET Web API that uses a very simple action filter:
public class ValidateModelAttribute : ActionFilterAttribute {
public override void OnActionExecuting(HttpActionContext actionContext) {
if (actionContext.ModelState.IsValid == false) {
var responseObject = new ApiResponse() {
Errors = actionContext.ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage)
};
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, responseObject);
}
}
}
My action filter simply enforces that the ModelState is valid; if not, it returns a 400 with all the error messages so the client can do whatever they wish with the data.
Here is how I'm using the attribute in my controller:
[ValidateModelAttribute]
public HttpResponseMessage Db(DbModel model) {
return Request.CreateResponse(HttpStatusCode.OK);
}
I'm trying to test the attribute using Visual Studio's testing framework:
[TestMethod]
public void CaptureApiDatabase_IfRequiredFieldMissing_ReturnHttpBadRequest() {
var controller = new ConfigureController();
controller.Request = new HttpRequestMessage();
controller.Configuration = new HttpConfiguration();
var validationAttribute = new ValidateModelAttribute();
validationAttribute.OnActionExecuting(controller.ActionContext);
Assert.IsTrue(controller.ActionContext.Response.StatusCode == HttpStatusCode.BadRequest);
}
This isn't working because I haven't provided the filter my data and as a result the ModelState is always valid. How do I build a context so I can test this filter attribute?

How do I test response data using nUnit?

Django has a very handy test client/dummy web browser that one can use in test cases to verify the correctness of HTTP responses (e.g., status codes, context/model data). It does not require you to have the web server running, as it deals directly with the framework to simulate the calls.
I'd really love an nUnit (or similar) equivalent that we can slip right into our test suites. We're working in MVC3 and 4, and want to check things like successful 301 redirects, that model validation is correct, and that ViewModel data is correct in the views.
What's the best solution for this?
ViewModel Data should be easy to check with the following:
public T GetViewModelFromResult<T>(ActionResult result) where T : class
{
Assert.IsInstanceOf<ViewResult>(result);
var model = ((ViewResult)result).Model;
Assert.IsInstanceOf<T>(model);
return model as T;
}
[Test]
public void TheModelHasTheOrder()
{
var controller = new MyController();
var result = controller.MyActionMethod();
var model = GetViewModelFromResult<MyModel>();
Assert.That(model, Is.SameAs(???));
}
As for the model validation, if you are using the out of the box .net property attributes like [Required] etc, you can be pretty sure they will work fine, and won't need testing.
To explicitly test the [Required] etc attributes on your object you will have extract the built in .net validation into another class. Then use that class in your controllers to validate your objects, instead of the Model.IsValid property on your controller.
The model validator class:
public class ModelValidator : IModelValidator
{
public bool IsValid(object entity)
{
return Validate(entity, new List<ValidationResult>());
}
public IEnumerable<ValidationResult> Validate(object entity)
{
var validationResults = new List<ValidationResult>();
Validate(entity, validationResults);
return validationResults;
}
private static bool Validate(object entity, ICollection<ValidationResult> validationResults)
{
if (entity != null)
{
var validationContext = new ValidationContext(entity, null, null);
return Validator.TryValidateObject(entity, validationContext, validationResults);
}
return false;
}
}
This could be verifiable in unit tests with the following:
public class MySampleEntity
{
[Required]
public string X { get; set; }
[Required]
public int Y { get; set; }
}
[TestFixture]
public class ModelValidatorTests
{
[Test]
public void GivenThePropertiesArePopulatedTheModelIsValid()
{
// arrange
var _validator = new ModelValidator();
var _entity = new MySampleEntity { X = "ABC", Y = 50 };
// act
var _result = _validator.IsValid(_entity);
// assert
Assert.That(_result, Is.True);
}
}

Mocking HttpSessionState in ASP.net for nunit testing

I've see n a lot of discussions surrounding HttpSessionState and asp.net MVC.
I'm trying to write tests for an asp.net application and wondering if it's possible to mock the HttpSessionState and if so, how?
I'm currently using Rhino Mocks and Nunit
Gilbert,
Maybe I'm too late for you. I'm using MSpec, but I think the concepts are similar. I needed to mock several components of the HttpContext in the controllers under test.
I started with these following classes to mock up the necessary (for my purposes) components in the HttpContextBase. I overrode only the necessary pieces inside the classes. Your needs will vary as to the mocks you need in the controller. It's fairly easy to add mocks as needed once you understand the pattern.
public class MockHttpContext : HttpContextBase
{
private readonly HttpRequestBase _request = new MockHttpRequest();
private readonly HttpServerUtilityBase _server = new MockHttpServerUtilityBase();
private HttpSessionStateBase _session = new MockHttpSession();
public override HttpRequestBase Request
{
get { return _request; }
}
public override HttpServerUtilityBase Server
{
get { return _server; }
}
public override HttpSessionStateBase Session
{
get { return _session; }
}
}
public class MockHttpRequest : HttpRequestBase
{
private Uri _url = new Uri("http://www.mockrequest.moc/Controller/Action");
public override Uri Url
{
get { return _url; }
}
}
public class MockHttpServerUtilityBase : HttpServerUtilityBase
{
public override string UrlEncode(string s)
{
//return base.UrlEncode(s);
return s; // Not doing anything (this is just a Mock)
}
}
public class MockHttpSession : HttpSessionStateBase
{
// Started with sample http://stackoverflow.com/questions/524457/how-do-you-mock-the-session-object-collection-using-moq
// from http://stackoverflow.com/users/81730/ronnblack
System.Collections.Generic.Dictionary<string, object> _sessionStorage = new System.Collections.Generic.Dictionary<string,object>();
public override object this[string name]
{
get { return _sessionStorage[name]; }
set { _sessionStorage[name] = value; }
}
public override void Add(string name, object value)
{
_sessionStorage[name] = value;
}
}
Here is how I setup the Controller Context to use the mocks (MSpec). This is setup for the actual tests on the contoller (the tests derive from this class)
public abstract class BlahBlahControllerContext
{
protected static BlahBlahController controller;
Establish context = () =>
{
controller = new BlahBlahController();
controller.ControllerContext = new ControllerContext()
{
Controller = controller,
RequestContext = new RequestContext(new MockHttpContext(), new RouteData()),
};
};
}
To further illustrate here is a test (Specification in MSpec world) that uses the mock session:
[Subject("ACCOUNT: Retrieve Password")]
public class retrieve_password_displays_retrieve_password2_page_on_success : BlahBlahControllerContext
{
static ActionResult result;
static RetrievePasswordModel model;
Establish context = () =>
{
model = new RetrievePasswordModel()
{
UserName = "Mike"
};
};
Because of = () =>
{
result = controller.RetrievePassword(model);
};
It should_return_a_RedirectToRouteResult = () =>
{
result.is_a_redirect_to_route_and().action_name().ShouldEqual("RetrievePassword2");
};
It session_should_contain_UN_value = () =>
{
controller.HttpContext.Session["UN"].ShouldEqual("Mike");
};
It session_should_contain_PQ_value = () =>
{
controller.HttpContext.Session["PQ"].ShouldEqual("Question");
};
}
I realize this doesn't use Rhino Mocks. I hope it illustrates the principles and readers can adopt it to their specific tools and methods.
If you need to instantiate exactly HttpSessionState for legacy code tests, you can leverage FormatterServices mechanism to get uninitialized object. To get it working it is needed to set private _container field though, like in internal constructor
Example:
var state = (HttpSessionState) System.Runtime.Serialization
.FormatterServices.GetUninitializedObject(typeof(HttpSessionState));
var containerFld = typeof(HttpSessionState).GetField(
"_container", BindingFlags.Instance | BindingFlags.NonPublic);
var itemCollection = new SessionStateItemCollection();
itemCollection["element"] = 1;
containerFld.SetValue(
state,
new HttpSessionStateContainer(
"1",
itemCollection,
new HttpStaticObjectsCollection(),
900,
true,
HttpCookieMode.UseCookies,
SessionStateMode.InProc,
false
)
);
look at the HttpSessionStateBase and HttpSessionStateWrapper classes in System.Web.Abstractions. HttpSessionStateBase is the abstract class from which HttpSessionState inherits, and HttpSessionStateWrapper is used to wrap a sealed class in an abstract class, which you can then mock in your tests.
A lot of the System.Web classes are sealed (for example, HttpSessionState), so it's a real pain to test your code when you have methods and classes that interact with them. One pattern I like to use to get around this looks like the following:
public void DoSomething(HttpSessionState state)
{
// take this HttpSeassionState and create an abstract HttpSessionStateBase
// instance
DoSomething(new HttpSessionStateWrapper(state));
}
internal void DoSomething(HttpSessionStateBase state)
{
// my actual logic for working with the session state
}
The public method is difficult to test, because HttpSessionState is sealed, and you can't mock it. However, the internal method operates on an HttpSessionStateBase instance, which you can mock. Note that I've marked it as internal because I don't want the outside world to be able to access that method. However, I do want my tests to be able to access that, so I'll modify my AssemblyInfo.cs to include something like this:
[assembly: InternalsVisibleTo("Vendor.Utilities.Tests")]
Finally, my test for this would look something like this:
[Test]
public void Test_DoSomething()
{
HttpSessionStateBase state = MockRepository.PartialMock<HttpSessionStateBase>();
state.Expect(s => ...);
MyClass.DoSomething(state);
state.VerifyAllExpectations();
}
Hope that helps. Good luck!
This is what I made up based on others contribution...
public class MockWebContext
{
public Mock<RequestContext> RoutingRequestContext { get; private set; }
public Mock<HttpContextBase> Http { get; private set; }
public Mock<HttpServerUtilityBase> Server { get; private set; }
public Mock<HttpResponseBase> Response { get; private set; }
public Mock<HttpRequestBase> Request { get; private set; }
public Mock<HttpSessionStateBase> Session { get; private set; }
public Mock<ActionExecutingContext> ActionExecuting { get; private set; }
public HttpCookieCollection Cookies { get; private set; }
private IDictionary items;
public MockWebContext()
{
RoutingRequestContext = new Mock<RequestContext>(MockBehavior.Loose);
ActionExecuting = new Mock<ActionExecutingContext>(MockBehavior.Loose);
Http = new Mock<HttpContextBase>(MockBehavior.Loose);
Server = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
Response = new Mock<HttpResponseBase>(MockBehavior.Loose);
Request = new Mock<HttpRequestBase>(MockBehavior.Loose);
Session = new Mock<HttpSessionStateBase>(MockBehavior.Loose);
Cookies = new HttpCookieCollection();
items = new Dictionary<string, object>();
RoutingRequestContext.SetupGet(c => c.HttpContext).Returns(Http.Object);
ActionExecuting.SetupGet(c => c.HttpContext).Returns(Http.Object);
Http.SetupGet(c => c.Request).Returns(Request.Object);
Http.SetupGet(c => c.Response).Returns(Response.Object);
Http.SetupGet(c => c.Server).Returns(Server.Object);
Http.SetupGet(c => c.Session).Returns(Session.Object);
Http.SetupGet(c => c.Items).Returns(items);
Request.Setup(c => c.Cookies).Returns(Cookies);
Request.Setup(c => c.RequestContext).Returns(RoutingRequestContext.Object);
Response.Setup(c => c.Cookies).Returns(Cookies);
Session.Setup(c =>
c.Add(It.IsAny<string>(), It.IsAny<object>())
).Callback((string key, object value)=> items.Add(key, value));
Session.Setup(c =>
c.Remove(It.IsAny<string>())
).Callback((string key) => items.Remove(key));
Session.Setup(c =>
c.Clear()
).Callback(() => items.Clear());
Session.Setup(c =>
c[It.IsAny<string>()]
).Returns((string key)=> items[key]);
}
}
Check out the MvcContrib project.

Resources