I have written Custom model binder for my MVC3 application. I desided to use the Custom model binder is because I am using Sessions and unit tests are failing because of them.
Now my problem is that About action does not accept any parameter but it need to pass the values stored in cart to view without using Session. Because with session it will fail the unit test. Model binder only works if I pass the cart as parameter to About.
Please suggest me if you have any ideas.
many Thanks
Model Binder
public class CartModelBinder : IModelBinder
{
private const string CartSessionKey = "Cart";
public object BindModel(ControllerContext controllerContext, ModelBindingContext modelBindingContext)
{
Cart cart = null;
if(IsCartExistInSession(controllerContext))
{
cart = GetCartFromSession(controllerContext);
}
else
{
cart = new Cart();
AddCartToSession(controllerContext, cart);
}
return cart;
}
private static Cart GetCartFromSession(ControllerContext controllerContext)
{
return controllerContext.HttpContext.Session[CartSessionKey] as Cart;
}
private static void AddCartToSession(ControllerContext controllerContext, Cart cart)
{
controllerContext.HttpContext.Session[CartSessionKey] = cart;
}
private static bool IsCartExistInSession(ControllerContext controllerContext)
{
return controllerContext.HttpContext.Session[CartSessionKey] != null;
}
}
Controller
[HttpPost]
public ActionResult AddToCartfromAbout(Cart cart, int productId = 2)
{
var product = _productRepository.Products.First(p => p.ProductId == productId);
cart.AddItem(product, 1);
return View("About");
}
public ActionResult About()
{
// Need something here to get the value of cart
return View(cart);
}
This Link may solve you problem. You need to download the source and DLLs from the link above and them you can assign the value to Session in Test.
[Test]
public void AddSessionStarShouldSaveFormToSession()
{
// Arrange
TestControllerBuilder builder = new TestControllerBuilder();
StarsController controller = new StarsController();
builder.InitializeController(controller);
controller.HttpContext.Session["NewStarName"] = "alpha c";
// Act
RedirectResult result = controller.Index() as RedirectResult;
// Assert
Assert.IsTrue(result.Url.Equals("Index"));
}
I would suggest use Moq (or any other tool for mocking your data) and pass the values on the controller constructor. (Not sure, but mabe use Dependency Injection would help on this if it is not too much over-engineering)
Related
So Basically i wrote a validator for my class with FluentValidation and also a filter to do the validation task for me in my webAPI project, so far it's OK but assume that my User class has firstname,lastname,email,password properties
and i have two routes (one for register and the other one for login)
and as you might have noticed required properties are different on these route.
Thus,should I really need to write individual validation for each and every action i have?because this makes a lot of code code duplication and it's hard to change.is there any way to just add required condition based on the request coming with single validation class?
Any suggestion???
A better practice would be to use a factory pattern for your validations and use a an action filter to short circuit bad requests. You could validate any action argument(Headers, Request Bodies, etc..) with something like this.
public class TestValidationAttribute : Attribute, IActionFilter
{
private string _requestModelName;
public TestValidationAttribute(string requestModelName)
{
_requestModelName = requestModelName;
}
public void OnActionExecuting(ActionExecutingContext context)
{
// using Microsoft.Extensions.DependencyInjection;
var services = context.HttpContext.RequestServices;
var accessor = services.GetService<IHttpContextAccessor>();
var factory = services.GetService<ITestValidatorFactory>();
var tokens = accessor.HttpContext.GetRouteData().DataTokens;
if (!tokens.TryGetValue("RouteName", out var routeNameObj))
{
throw new Exception($"Action doesn't have a named route.");
}
var routeName = routeNameObj.ToString();
var validator = factory.Create(routeName);
if (!context.ActionArguments.TryGetValue(_requestModelName, out var model))
{
throw new Exception($"Action doesn't have argument named {_requestModelName}.");
}
TestModel test;
try
{
test = (TestModel) model;
}
catch (InvalidCastException)
{
throw new Exception($"Action argument can't be casted to {nameof(TestModel)}.");
}
var validation = validator.Validate(test);
if (!validation.Successful)
{
context.Result = new BadRequestObjectResult(validation.ResponseModel);
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
public class TestController : Controller
{
[HttpPost]
[Route("Test/{id}", Name = "TestGet")]
[TestValidation("model")]
public IActionResult Test(TestModel model)
{
return Ok();
}
}
public class ValidationResult
{
public bool Successful { get; }
public ResponseModel ResponseModel { get; }
}
public class TestModel
{
}
public interface ITestValidator
{
ValidationResult Validate(TestModel model);
}
public interface ITestValidatorFactory
{
ITestValidator Create(string routeName);
}
I wanted to save notification in TempData and shown to user. I create extension methods for this and implement a class which Extends from ActionResult. I need to access TempData in override ExecuteResult method with ActionContext.
Extension Method:
public static IActionResult WithSuccess(this ActionResult result, string message)
{
return new AlertDecoratorResult(result, "alert-success", message);
}
Extends ActionResult class.
public class AlertDecoratorResult : ActionResult
{
public ActionResult InnerResult { get; set; }
public string AlertClass { get; set; }
public string Message { get; set; }
public AlertDecoratorResult(ActionResult innerResult, string alertClass, string message)
{
InnerResult = innerResult;
AlertClass = alertClass;
Message = message;
}
public override void ExecuteResult(ActionContext context)
{
ITempDataDictionary tempData = context.HttpContext.RequestServices.GetService(typeof(ITempDataDictionary)) as ITempDataDictionary;
var alerts = tempData.GetAlert();
alerts.Add(new Alert(AlertClass, Message));
InnerResult.ExecuteResult(context);
}
}
Call extension method from controller
return RedirectToAction("Index").WithSuccess("Category Created!");
I get 'TempData ' null , How can I access 'TempData' in 'ExecuteResult' method.
I was literally trying to do the exact same thing today (have we seen the same Pluralsight course? ;-) ) and your question led me to find how to access the TempData (thanks!).
When debugging I found that my override on ExecuteResult was never called, which led me to try the new async version instead. And that worked!
What you need to do is override ExecuteResultAsync instead:
public override async Task ExecuteResultAsync(ActionContext context)
{
ITempDataDictionaryFactory factory = context.HttpContext.RequestServices.GetService(typeof(ITempDataDictionaryFactory)) as ITempDataDictionaryFactory;
ITempDataDictionary tempData = factory.GetTempData(context.HttpContext);
var alerts = tempData.GetAlert();
alerts.Add(new Alert(AlertClass, Message));
await InnerResult.ExecuteResultAsync(context);
}
However, I have not fully understood why the async method is called as the controller is not async... Need to do some reading on that...
I find out the way to get the TempData. It need to get from ITempDataDictionaryFactory
var factory = context.HttpContext.RequestServices.GetService(typeof(ITempDataDictionaryFactory)) as ITempDataDictionaryFactory;
var tempData = factory.GetTempData(context.HttpContext);
I need to access a property inside a custom DataAnnotation attribute. How can I access this attribute in order to set the response value? The attribute is added to the model property.
public class BirthDateAttribute : ValidationAttribute
{
public string ErrorCode { get; set; }
....
}
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
List<Errors> errors = new List<Errors>();
// Set error message and errorCode
foreach (var key in keys)
{
if (!actionContext.ModelState.IsValidField(key))
{
error.Add(new HttpResponseError
{
Code = ???????????,
Message = actionContext.ModelState[key].Errors.FirstOrDefault().ErrorMessage
});
}
}
// Return to client
actionContext.Response = actionContext.Request.CreateResponse(
HttpStatusCode.BadRequest, errors);
}
}
}
Assuming that the custom attribute is applied to the controller, you can try following in the OnActionExecuting event. This similar thing works with MVC controller but should work with API controller too.
var att = actionContext.ControllerContext.GetType().GetCustomAttributes(typeof(BirthDateAttribute), false)[0] as BirthDateAttribute;
string errorCode = att.ErrorCode;
As mentioned by OP, if this is on a class (Model), it should be pretty starightforward because the type is already known. Replace the Model class.
var att = <<ModalClass>>.GetCustomAttributes(typeof(BirthDateAttribute), false)[0] as BirthDateAttribute;
string errorCode = att.ErrorCode;
I've created WebApi controller based on following tutorial: sebastienros website
My modules name is Company.Accounts.
public class AccountController : ApiController
{
[HttpPost]
public string LogIn([FromBody] UserModel user)
{
// this is working
return this.accountService.LogIn(user.UserName, user.Password);
}
[HttpPut]
public string SomePuthMethod([FromBody] UserModel user)
{
// method not allowed
// some code...
}
}
Implementation of IHttpRouteProvider looks like:
private IEnumerable<RouteDescriptor> GetAccountRoute()
{
yield return new HttpRouteDescriptor
{
Name = "Account",
Priority = 10,
RouteTemplate = "Api/Account",
Defaults = new
{
area = "Company.Accounts",
controller = "Account"
}
};
}
Unfortunately, everything except GET and POST *is not working*. I'm getting simple
Method not allowed.
What's wrong? My Orchard version is 1.7.1.
You put them in the MethodNames public HttpResponseMessage Post([FromBody]...){}
I am working on an MVC project where controller actions deal with Assets. Different controllers take in the assetId parameter in different way: Some controllers simply get int assetId, other int id, and other using a complex object AssetDTO dto (which contains a property that holds the assetId)
I am writing an ActionFilter that is added to the action method and is provided with the actionParameter name where I can get the asset value.
Action Method:
[AssetIdFilter("assetId")]
public ActionResult Index(int assetId)
{
...
}
The attribute is defined as:
public class AssetIdFilterAttribute : ActionFilterAttribute
{
public string _assetIdParameterKey { get; set; }
public AssetIdFilterAttribute (string assetIdParameterKey)
{
_assetIdParameterKey = assetIdParameterKey;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
int assetId;
if (Int32.TryParse(filterContext.ActionParameters[_assetIdParameterKey].ToString(), out assetId))
{
......
}
}
This works as expected, but will only work when the assetId is provided as a primitive. I am not sure what to do when the assetId is provided within a complex object into the action method.
Will I need to parse each object differently depending on the type? I am hoping I can specify some kind of dot-notation in the AssetIdFilter to tell it where the assetId is located: dto.assetId
Any way I can use dynamics? or reflection?? ect.???
and here dynamic comes to the rescue.you can change the actionFilterAttribute to be :
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
dynamic assetIdHolder = filterContext.ActionParameters[_assetIdParameterKey];
if (assetIdHolder.GetType().IsPrimitive)
{
//do whatever with assetIdHolder
}
else
{
//do whatever with assetIdHolder.assetId
}
}
cheers!
Well, yes, you answered your question. One way would be to use dot notation:
//simple case:
[AssetId("id")]
public ActionResult Index(string id) {
//code here
}
//complex case:
[AssetId("idObj", AssetIdProperty = "SubObj.id")]
public ActionResult index(IdObject idObj) {
//code here
}
And AssetIdAttribute is as follows:
public class AssetIdAttribute : ActionFilterAttribute
{
public string _assetIdParameterKey { get; set; }
public string AssetIdProperty { get; set; }
public AssetIdFilterAttribute(string assetIdParameterKey)
{
_assetIdParameterKey = assetIdParameterKey;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
int assetId;
var param = filterContext.ActionParameters[_assetIdParameterKey];
int.TryParse(GetPropertyValue(param, this.AssetIdProperty).ToString(), out assetId);
//you code continues here.
}
private static string GetPropertyValue(object souce, string property)
{
var propNames = string.IsNullOrWhiteSpace(property) || !property.Contains('.') ? new string[] { } : property.Split('.');
var result = souce;
foreach (var prop in propNames)
{
result = result.GetType().GetProperty(prop).GetValue(result);
}
return result.ToString();
}
}
The code does not have null checks when calling ToString and when calling GetProperty though. Also, it does not check the success of TryParse. Please apply these corrections when used.
Maybe this code could be written using dynamic, but at the end dynamic usage is compiled into object using reflection (something like what I have done here), thus no big difference to me.
Also, maybe it would be more clear to have a parameter like "idObj.SubObj.id", but that again depends on the preference, and the code will become a little bit more complex.