How to validate parameters sent to an Azure function? - .net-core

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/

Related

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).

Data Annotations to sanitize request and response before logging

I'm looking for a reliable solution to log details of requests and responses made to and from our controllers. However, some of the data passing through contains sensitive information that should not be written to a log.
In the controller, the inbound request is bound to a single model from the request body, and as the request is answered, a single model is passed to the Ok() result like this (very simplified):
[HttpGet]
[Route("Some/Route")]
public IHttpActionResult SomeController([FromBody] RequestType requestObj)
{
ResponseType responseObj = GetResponse(requestObj)
return this.Ok(responseObj);
}
Now my goal is to somehow log the contents of the request and response object at the beginning and end of the controller, respectively. What I would like to do is bind the models first, then log out their attributes. An example of the RequestType is something like:
public class RequestType
{
public string SomeAttribute { get; set; }
public string AnotherAttribute { get; set; }
public string Password{ get; set; }
}
And the log would look something like:
[date-time] Request to SomeController:
SomeAttribute: "value_from_request"
AnotherAttribute: "another_value"
Password: "supersecret123"
Now clearly we don't want the password to be logged. So I would like to create a custom data annotation that would not log certain fields. Its use would look like this (updated RequestType):
public class RequestType
{
public string SomeAttribute { get; set; }
public string AnotherAttribute { get; set; }
[SensitiveData]
public string Password{ get; set; }
}
Where would I start with this? I'm not incredibly familliar with .NET, but know that there are many sort of magic classes that can be subclassed to override some of their functionality. Is there any such class that can help here? Even better, is there any way to do this during the model binding? So we could catch errors that occur during model binding as well?
We should be able to achieve what you're looking for with an ActionFilterAttribute.
Capture Requests Attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class CaptureRequestsAttribute : ActionFilterAttribute // *IMPORTANT* This is in the System.Web.Http.Filters namespace, not System.Web.Mvc
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var messages = actionContext.ActionArguments.Select(arg => GetLogMessage(arg.Value));
var logMessage = $"[{DateTime.Now}] Request to " +
$"{actionContext.ControllerContext.Controller}]:\n{string.Join("\n", messages)}";
WriteToLog(logMessage);
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var result = actionExecutedContext.Response.Content as ObjectContent;
var message = GetLogMessage(result?.Value);
var logMessage = $"[{DateTime.Now}] Response from " +
$"{actionExecutedContext.ActionContext.ControllerContext.Controller}:\n{message}";
WriteToLog(logMessage);
base.OnActionExecuted(actionExecutedContext);
}
private static void WriteToLog(string message)
{
// todo: write you logging stuff here
}
private static string GetLogMessage(object objectToLog)
{
if (objectToLog == null)
{
return string.Empty;
}
var type = objectToLog.GetType();
var properties = type.GetProperties();
if (properties.Length == 0)
{
return $"{type}: {objectToLog}";
}
else
{
var nonSensitiveProperties = type
.GetProperties()
.Where(IsNotSensitiveData)
.Select(property => $"{property.Name}: {property.GetValue(objectToLog)}");
return string.Join("\n", nonSensitiveProperties);
}
}
private static bool IsNotSensitiveData(PropertyInfo property) =>
property.GetCustomAttributes<SensitiveDataAttribute>().Count() == 0;
}
Sensitive Data Attribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class SensitiveDataAttribute : Attribute
{
}
Then, you can just add it to your WebApi controller (or a specific method in it):
[CaptureRequests]
public class ValuesController : ApiController
{
// .. methods
}
And finally your models can just add the SensitiveDataAttribute:
public class TestModel
{
public string Username { get; set; }
[SensitiveData]
public string Password { get; set; }
}
This does not make use of DataAnnotations,however, One way that comes to mind would be to use the serialization. If your payload is within a reasonable size you could serialize and deserialize your RequestType class when reading and writing to/from a log. This would require a custom serialization format or making use of the default, xml.
[Seriliazeble()]
public class RequestType
{
public string SomeAttribute { get; set; }
public string AnotherAttribute { get; set; }
[NonSerialized()]
public string Password{ get; set; }
}
Using the above attribute will omit Password from serialization. Then you copuld proceed to Logger.Log(MySerializer.Serialize(MyRequest)); and your sensitive data will be omitted.
This link describes the approach in detail.
For xml serialization, simply use the XmlSerializer class.
public class MySerializationService
{
public string SerializeObject(object item)
{
XmlSerializer serializer = new XmlSerializer(item.GetType());
System.IO.MemoryStream aMemStr = new System.IO.MemoryStream();
System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(aMemStr, null);
serializer.Serialize(writer, item);
string strXml = System.Text.Encoding.UTF8.GetString(aMemStr.ToArray());
return strXml;
}
public object DeSerializeObject(Type objectType, string objectString)
{
object obj = null;
XmlSerializer xs = new XmlSerializer(objectType);
obj = xs.Deserialize(new StringReader(objectString));
return obj;
}
}
Then using the above or similar methods you can read and write in a custom format.
Write :
string logData=new MySerializationService().SerializeObject(myRequest);
Read :
RequestType loggedRequest= (RequestType)new MySerializationService().DeSerializeObject(new RequestType().GetType(), logData);

Asp Mvc 6 Model Validation with a service in custom ValidationAttribute

TLDR: In Asp Mvc 6 how do I perform model validation with a service using data annotations? What are the alternatives?
I have a very simple model
public class MyModel
{
[Required]
public string Name { get; set; }
}
I also have a service that exposes some simple validation methods
public interface IMyService
{
string[] ReservedWords { get; }
bool IsValidName(string name);
// Internally calls IsValidName and throws an Exception if the name is invalid
void Save(MyModel myModel);
// ... snip
}
And I have wired up my controller like so
public class MyController : Controller
{
private readonly IMyService _service;
public MyController(IMyService service)
{
_service = service;
}
// ... snip
public IActionResult Post(MyModel myModel)
{
if (!_service.IsValidName(input?.Name))
{
ModelState.AddModelError(nameof(MyModel.Name), "Invalid Name");
}
if (!ModelState.IsValid)
{
return View(myModel);
}
_service.Save(myModel);
return RedirectToAction(nameof(Index));
}
}
It feels a bit clucky to have 2 stages of validation - automatic model validation then manually performing service validation. I was hoping that something simialr to this would work
public class MyModel
{
[ServiceValidation(nameof(IMyService), nameof(IMyService.IsValidName)]
[Required]
public string Name { get; set; }
}
public ServiceValidationAttribute : ValidationAttribute
{
private readonly Type _interfaceOrClass;
private readonly string _methodOrProperty;
public ServiceValidationAttribute(Type interfaceOrClass, string methodOrProperty)
{
_interfaceOrClass = interfaceOrClass;
_methodOrProperty = methodOrProperty;
}
public override bool RequiresValidationContext => true;
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var service = validationContext.GetService(_interfaceOrClass);
// Extension method in shared library to assist with reflection
bool isValid = _interfaceOrClass.ValueForMethodOrPropertyNamed<bool>(service, _methodOrProperty, value);
return isValid
? ValidationResult.Success
: new ValidationResult(ErrorMessage);
}
}
However var serivce is always null, is there any way around this? I have wired up the IMyService to an implementation in the Startup.cs as it is available in the Controller.
Alternatively is there a better way of adding to the ModelState with a service?

NHibernate in Web API ASP.NET: No session bound to the current context

I'm new to NHibernate and trying to use it in ASP.NET WEB API. Firstly I used it successfully with one table named "Category" which the controller class is as follow:
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using TestMVCProject.Web.Api.HttpFetchers;
using TestMVCProject.Web.Api.Models;
using TestMVCProject.Web.Api.TypeMappers;
using TestMVCProject.Web.Common;
//using TestMVCProject.Web.Common.Security;
using NHibernate;
namespace TestMVCProject.Web.Api.Controllers
{
[LoggingNHibernateSession]
public class CategoryController : ApiController
{
private readonly ISession _session;
private readonly ICategoryMapper _categoryMapper;
private readonly IHttpCategoryFetcher _categoryFetcher;
public CategoryController(
ISession session,
ICategoryMapper categoryMapper,
IHttpCategoryFetcher categoryFetcher)
{
_session = session;
_categoryMapper = categoryMapper;
_categoryFetcher = categoryFetcher;
}
public IEnumerable<Category> Get()
{
return _session
.QueryOver<Data.Model.Category>()
.List()
.Select(_categoryMapper.CreateCategory)
.ToList();
}
public Category Get(long id)
{
var category = _categoryFetcher.GetCategory(id);
return _categoryMapper.CreateCategory(category);
}
public HttpResponseMessage Post(HttpRequestMessage request, Category category)
{
var modelCategory = new Data.Model.Category
{
Description = category.Description,
CategoryName = category.CategoryName
};
_session.Save(modelCategory);
var newCategory = _categoryMapper.CreateCategory(modelCategory);
//var href = newCategory.Links.First(x => x.Rel == "self").Href;
var response = request.CreateResponse(HttpStatusCode.Created, newCategory);
//response.Headers.Add("Location", href);
return response;
}
public HttpResponseMessage Delete()
{
var categories = _session.QueryOver<Data.Model.Category>().List();
foreach (var category in categories)
{
_session.Delete(category);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public HttpResponseMessage Delete(long id)
{
var category = _session.Get<Data.Model.Category>(id);
if (category != null)
{
_session.Delete(category);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public Category Put(long id, Category category)
{
var modelCateogry = _categoryFetcher.GetCategory(id);
modelCateogry.CategoryName = category.CategoryName;
modelCateogry.Description = category.Description;
_session.SaveOrUpdate(modelCateogry);
return _categoryMapper.CreateCategory(modelCateogry);
}
}
}
But when I add The "Product" table which has a foreign key of the Category table, the product controller doesn't work and throws below exception:
No session bound to the current context
ProductController class is as follow:
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using TestMVCProject.Web.Api.HttpFetchers;
using TestMVCProject.Web.Api.Models;
using TestMVCProject.Web.Api.TypeMappers;
using TestMVCProject.Web.Common;
//using TestMVCProject.Web.Common.Security;
using NHibernate;
namespace TestMVCProject.Web.Api.Controllers
{
[LoggingNHibernateSession]
public class ProductController : ApiController
{
private readonly ISession _session;
private readonly IProductMapper _productMapper;
private readonly IHttpProductFetcher _productFetcher;
public ProductController(
ISession session,
IProductMapper productMapper,
IHttpProductFetcher productFetcher)
{
_session = session;
_productMapper = productMapper;
_productFetcher = productFetcher;
}
public IEnumerable<Product> Get()
{
return _session
.QueryOver<Data.Model.Product>()
.List()
.Select(_productMapper.CreateProduct)
.ToList();
}
public Product Get(long id)
{
var product = _productFetcher.GetProduct(id);
return _productMapper.CreateProduct(product);
}
public HttpResponseMessage Post(HttpRequestMessage request, Product product)
{
var modelProduct = new Data.Model.Product
{
Description = product.Description,
ProductName = product.ProductName
};
_session.Save(modelProduct);
var newProduct = _productMapper.CreateProduct(modelProduct);
//var href = newproduct.Links.First(x => x.Rel == "self").Href;
var response = request.CreateResponse(HttpStatusCode.Created, newProduct);
//response.Headers.Add("Location", href);
return response;
}
public HttpResponseMessage Delete()
{
var categories = _session.QueryOver<Data.Model.Product>().List();
foreach (var product in categories)
{
_session.Delete(product);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public HttpResponseMessage Delete(long id)
{
var product = _session.Get<Data.Model.Product>(id);
if (product != null)
{
_session.Delete(product);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public Product Put(long id, Product product)
{
var modelProduct = _productFetcher.GetProduct(id);
modelProduct.ProductName = product.ProductName;
modelProduct.Description = product.Description;
_session.SaveOrUpdate(modelProduct);
return _productMapper.CreateProduct(modelProduct);
}
}
}
and the mapping class for Product table:
using TestMVCProject.Data.Model;
using FluentNHibernate.Mapping;
namespace TestMVCProject.Data.SqlServer.Mapping
{
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.ProductId);
Map(x => x.ProductName).Not.Nullable();
Map(x => x.Description).Nullable();
Map(x => x.CreateDate).Not.Nullable();
Map(x => x.Price).Not.Nullable();
References<Category>(x => x.CategoryId).Not.Nullable();
}
}
}
What is wrong?
Your snippets are missing the way, how the ISessionFactory is created and how ISession is passed into your controllers... You should follow this really comprehensive story (by Piotr Walat):
NHibernate session management in ASP.NET Web API
Where you can see that we, can use 2.3. Contextual Sessions:
NHibernate.Context.WebSessionContext - stores the current session in HttpContext. You are responsible to bind and unbind an ISession instance with static methods of class CurrentSessionContext.
The configuration
<session-factory>
..
<property name="current_session_context_class">web</property>
</session-factory>
In the article you can check that we need at the app start initialize factory (just an extract):
public class WebApiApplication : System.Web.HttpApplication
{
private void InitializeSessionFactory() { ... }
protected void Application_Start()
{
InitializeSessionFactory();
...
Next we should create some AOP filter (just an extract):
public class NhSessionManagementAttribute : ActionFilterAttribute
{
...
public override void OnActionExecuting(HttpActionContext actionContext)
{
// init session
var session = SessionFactory.OpenSession();
...
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
// close session
...
session = CurrentSessionContext.Unbind(SessionFactory);
}
For more details check the source mentioned above
Your approach of passing the session to the constructor of the controller factory does not seems to be working, there are a few ways to do this
1. Using dependency injection
If you are using a dependency injection framework, you have to configure controller so that it's constructed per request, it should looks like this (I have used the code for Ninject)
Step 1 - setup the session for injection
public class DIModule : NinjectModule
{
public override void Load()
{
this.Bind<ISessionFactory>()... bind to the session factory
this.Bind<ISession>().ToMethod(ctx => ctx.Kernel.Get<ISessionFactory>().OpenSession())
.InRequestScope();
}
private ISession CreateSessionProxy(IContext ctx)
{
var session = (ISession)this.proxyGenerator.CreateInterfaceProxyWithoutTarget(typeof(ISession), new[] { typeof(ISessionImplementor) }, ctx.Kernel.Get<SessionInterceptor>());
return session;
}
}
Step 2 - Create the controller factory so that it will inject the session when resolving
public class NinjectControllerFactory : DefaultControllerFactory, IDependencyResolver
{
private IDependencyResolver _defaultResolver;
public NinjectControllerFactory(IDependencyResolver defaultResolver)
{
_defaultResolver = defaultResolver;
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
return controllerType == null
? null
: (IController)DependencyKernel.Kernel.Get(controllerType);
}
public IDependencyScope BeginScope()
{
return this;
}
public object GetService(Type serviceType)
{
try
{
return DependencyKernel.Kernel.Get(serviceType);
}
catch (Exception)
{
return GetService(serviceType);
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
object item = DependencyKernel.Kernel.Get(serviceType);
return new List<object>() {item};
}
catch (Exception)
{
return GetServices(serviceType);
}
}
public void Dispose()
{
}
}
Step 3 - Register the controller factory
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var factory = new NinjectControllerFactory(GlobalConfiguration.Configuration.DependencyResolver);
ControllerBuilder.Current.SetControllerFactory(factory);
GlobalConfiguration.Configuration.DependencyResolver = factory;
}
}
Now what will happen is that when your controller is created it will inject the a new NH session per each request.
2. Using a filter
This is much simpler, but you may need to change your controllers a bit this to work,
Step 1 - Setup the correct session context for the factory
_sessionFactory = CreateConfiguration()
.ExposeConfiguration(c => c.SetProperty("current_session_context_class","web"))
.BuildSessionFactory();
Step 2 - Create the filter
public class SessionPerRequestAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var session = SessionFactory.OpenSession();
NHibernate.Context.CurrentSessionContext.Bind(session);
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var session = SessionFactory.GetCurrentSession();
session.Flush();
session.Clear();
session.Close();
base.OnActionExecuted(actionExecutedContext);
}
}
Step 3 - Register the filter in global configuration
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//Do other config here
config.Filters.Add(new SessionPerRequestAttribute());
}
}
Step 4 - Modify your controller a bit,
public class CategoryController : ApiController
{
private readonly ICategoryMapper _categoryMapper;
private readonly IHttpCategoryFetcher _categoryFetcher;
public CategoryController(
ICategoryMapper categoryMapper,
IHttpCategoryFetcher categoryFetcher)
{
_categoryMapper = categoryMapper;
_categoryFetcher = categoryFetcher;
}
public IEnumerable<Category> Get()
{
var session = SessionFactory.GetCurrentSession();
return session
.QueryOver<Data.Model.Category>()
.List()
.Select(_categoryMapper.CreateCategory)
.ToList();
}
}
Here what happens is, when a request comes it will create a new session and it is bound to the request context and same is used for the web API method.

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