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
Related
I m new to Azure Function. I m used to code with WebApi where I have an ActionExecutingContext which helps to validate the ModelState.
I created an ActionFilterAttribute which do it automatically:
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public class ValidateModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
//Do something
}
}
}
How can I do that or something similar with Azure function?
For example with this DTO with a name property set as Required:
public class TestDto
{
[Required]
public string Name { get; set; }
}
I created an easy extension which validate the object and set an out parameter with the collection of errors.
public static class ObjectExtensions
{
public static bool IsValid(this object o, out ICollection<ValidationResult> validationResults)
{
validationResults = new List<ValidationResult>();
return Validator.TryValidateObject(o, new ValidationContext(o, null, null), validationResults, true);
}
}
So in my Azure function, here is what I have:
[FunctionName("Create")]
public async Task<IActionResult> CreateAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "test/")] TestDto dto,
CancellationToken cts,
ILogger log)
{
if (!dto.IsValid(validationResults: out var validationResults))
{
return new BadRequestObjectResult($"{nameof(TestDto)} is invalid: {string.Join(", ", validationResults.Select(s => s.ErrorMessage))}");
}
var result = await _testManager.CreateAsync(new Test() { Name = dto.Name }, cts);
return new OkObjectResult(result);
}
I think you could also write a custom attribute for this that implements the OnExecutingAsync and perform the validation in there, see https://www.c-sharpcorner.com/article/do-you-know-azure-function-have-function-filters/
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());
});
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?
I'm using ServiceStack / StructureMap / Moq. The service makes a call to Session, which is type ServiceStack.CacheAccess.ISession. For unit tests, I created a Mock object using Moq, and added it to the StructureMap configuration:
protected Mock<ISession> sessionMock = new Mock<ISession>();
ObjectFactory.Configure(
cfg =>
{
cfg.For<ISession>().Use(sessionMock.Object);
However, I was not surprised when the Session object was null -- I'm pretty sure I'm leaving out a step. What else do I need to do to fill my Session property with a mock object?
[EDIT] Here's a simple test scenario
Code to test. Simple request / service
[Route("getKey/{key}")]
public class MyRequest:IReturn<string>
{
public string Key { get; set; }
}
public class MyService:Service
{
public string Get(MyRequest request)
{
return (string) Session[request.Key];
}
}
The base test class and MockSession classes
// test base class
public abstract class MyTestBase : TestBase
{
protected IRestClient Client { get; set; }
protected override void Configure(Container container)
{
// this code is never reached under any of my scenarios below
container.Adapter = new StructureMapContainerAdapter();
ObjectFactory.Initialize(
cfg =>
{
cfg.For<ISession>().Singleton().Use<MockSession>();
});
}
}
public class MockSession : ISession
{
private Dictionary<string, object> m_SessionStorage = new Dictionary<string, object>();
public void Set<T>(string key, T value)
{
m_SessionStorage[key] = value;
}
public T Get<T>(string key)
{
return (T)m_SessionStorage[key];
}
public object this[string key]
{
get { return m_SessionStorage[key]; }
set { m_SessionStorage[key] = value; }
}
}
And tests. See comments for where I'm seeing the failure. I didn't really expect versions 1 & 2 to work, but hoped version 3 would.
[TestFixture]
public class When_getting_a_session_value:MyTestBase
{
[Test]
public void Test_version_1()
{
var session = ObjectFactory.GetInstance<MockSession>();
session["key1"] = "Test";
var request = new MyRequest {Key = "key1"};
var client = new MyService(); // generally works fine, except for things like Session
var result = client.Get(request); // throws NRE inside MyService
result.ShouldEqual("Test");
}
[Test]
public void Test_version_2()
{
var session = ObjectFactory.GetInstance<MockSession>();
session["key1"] = "Test";
var request = new MyRequest {Key = "key1"};
var client = ObjectFactory.GetInstance<MyService>();
var result = client.Get(request); // throws NRE inside MyService
result.ShouldEqual("Test");
}
[Test]
public void Test_version_3()
{
var session = ObjectFactory.GetInstance<MockSession>();
session["key1"] = "Test";
var request = new MyRequest {Key = "key1"};
var client = CreateNewRestClient();
var result = client.Get(request); // throws NotImplementedException here
result.ShouldEqual("Test");
}
}
It looks like you're trying to create unit tests, but you're using an AppHost like you wound an Integration test. See this previous answer for differences between the two and docs on Testing.
You can mock the Session by registering an instance in Request.Items[Keywords.Session], e.g:
[Test]
public void Can_mock_IntegrationTest_Session_with_Request()
{
using var appHost = new BasicAppHost(typeof(MyService).Assembly).Init();
var req = new MockHttpRequest();
req.Items[Keywords.Session] = new AuthUserSession {
UserName = "Mocked"
};
using var service = HostContext.ResolveService<MyService>(req);
Assert.That(service.GetSession().UserName, Is.EqualTo("Mocked"));
}
Otherwise if you set AppHost.TestMode=true ServiceStack will return the IAuthSession that's registered in your IOC, e.g:
[Test]
public void Can_mock_UnitTest_Session_with_IOC()
{
using var appHost = new BasicAppHost
{
TestMode = true,
ConfigureContainer = container =>
{
container.Register<IAuthSession>(c => new AuthUserSession {
UserName = "Mocked",
});
}
}.Init();
var service = new MyService {
Request = new MockHttpRequest()
};
Assert.That(service.GetSession().UserName, Is.EqualTo("Mocked"));
}
I've got a thread that sends emails around. I need to generate ActionLinks as part of the content of the email so the user can click on the link and be redirected to the website, exactly to the required page. I tried to instantiate a UrlHelper class and use it's Action method to generate the link but since threads don't run within the context of any request I get exceptions at the time of generating the ActionLink.
How can I do this?
You need to fake HttpContextBase and pass this to an UrlHelper which you can use in a thread without an HttpContext. Here is the rough idea, although you will need to create a class around it etc, this is a quick proof of concept as unit tests don't have an HttpContext either.
[TestFixture]
public class RouteTestClass
{
private UrlHelper helper;
public RouteTestClass()
{
MvcApplication.RegisterRoutes(RouteTable.Routes); //You dont need to do this if its done in global.asax!
var c = new RequestContext(new FakeContext(), new RouteData());
helper = new UrlHelper(c, RouteTable.Routes);
}
[Test]
public void TestGetHomeIndex()
{
var url = helper.Action("Index", "Home");
Assert.AreEqual("/",url);
}
}
public class FakeContext : HttpContextBase
{
public override HttpRequestBase Request { get { return new FakeRequest(); } }
public override HttpResponseBase Response { get { return new FakeResponse(); } }
}
public class FakeRequest : HttpRequestBase
{
public override string ApplicationPath { get { return "/"; } }
public override NameValueCollection ServerVariables { get { return new NameValueCollection(); } }
}
public class FakeResponse : HttpResponseBase
{
public override string ApplyAppPathModifier(string virtualPath)
{
return virtualPath;
}
}
Edit
Looking at this answer, I tidied the code up a little as I don't need to create fakes for HttpRequestBase and HttpResponseBase myself.
[TestFixture]
public class RouteTestClass
{
private UrlHelper helper;
public RouteTestClass()
{
MvcApplication.RegisterRoutes(RouteTable.Routes);
var req = new HttpRequest("/", "http://www.yoururl.com", "");
var resp = new HttpResponse(new StringWriter());
var httpContext = new HttpContext(req, resp);
var c = new RequestContext(new HttpContextWrapper(httpContext), new RouteData());
helper = new UrlHelper(c, RouteTable.Routes);
}
[Test]
public void TestGetHomeIndex()
{
var url = helper.Action("Index", "Home");
Assert.AreEqual("/",url);
}
}
You can give the thread access to an existing UrlHelper by passing it to the thread starter. If your thread is started from a controller, just pass the UrlHelper in the controller's Url property:
new Thread(
urlHelper =>
{
var url =
((UrlHelper)urlHelper)
.Action("Index", "Home", new { Id = 5 });
// use url here
}
).Start(Url);