I am exactly at the situation where this person (Controller ModelState with ModelStateWrappper) is except that I am using Castle Windsor DI. I am trying to validate my model's business logic side in my service before I save the model pass it to the data store. if the validation fails I want to get the model state errors and display in my view.
(I am following this article as a guide to implement validation from service: http://www.asp.net/mvc/tutorials/older-versions/models-(data)/validating-with-a-service-layer-cs)
Below is my sample code,
//My controller code
public class TimeEntryController : TempoBaseController
{
public TimeEntryController(TimeService service, UserService userService)
{
_service = service;
_userService = userService;
}
[Authorize(Roles = "Worker")]
public ActionResult Index()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(EntryLogDto entry)
{
if(_service.AddEntryLog(entry))
{
return ViewSuccess();
}else
{
return ViewFailue();
}
}
}
//My Service class
public class TimeService : GenericService
{
public TimeService(IRepository repository, IValidationDictionary validationDictionary, UserManager<ApplicationUser> userManager)
: base(repository, validationDictionary, userManager)
{
}
public bool AddEntryLog(EntryLogDto log)
{
if (!ValidateEntryLog(log))
{
//return false;
}
else
{
//insert entry into database and return true
}
}
protected bool ValidateEntryLog(EntryLogDto log)
{
//Check if the entry overlaps with any other entries
bool res = _repository.Find<EntryLogDto>(//my linq logic);
if (res)
{
_validationDictionary.IsValid = true;
}else
{
_validatonDictionary.AddError("Entry", "Entry Overlaps.");
_validationDictionary.IsValid = false;
}
return _validationDictionary.IsValid;
}
}
//Validation class
public class TempoValidation : IValidationDictionary
{
private ModelStateDictionary _modelState;
public TempoValidation(ModelStateDictionary modelState) // Issue is how am I gona give it this as the ModelStateDictiona ry is controller specific
{
_modelState = modelState;
}
public void AddError(string key, string error)
{
_modelState.AddModelError(key, error);
}
public bool IsValid
{
get { return _modelState.IsValid; }
}
}
//Global.asax castle compnonent registration method
container
.Register(Component
.For<Tempo.Model.Configuration.TempoDbContext>()
.LifestylePerWebRequest()
.DependsOn(new { connectionString }))
.Register(Component
.For<DbRepositories.ITempoDataContextFactory>()
.AsFactory())
.Register(Component
.For<IRepository>()
.ImplementedBy<Tempo.Repositories.EntityFrameworkRepository.Repository>())
.Register(Component
.For<TimeService>().LifestyleTransient())
I am injecting IValidationDictionary in my service class where I set the model state depending on the validation result. Is there a way I can pass in the model state of the controller when I use it? I don't know how to approach this, I have many controllers and I don't know how I will/when will I pass the respective controller's model state (I would like to do that by DI if its possible )... I don't know if castle can create a separate instance of TempoValidation class for each controller??
I know that this is impossible to do this by default, but you can use Fluent Validation to achieve this.
Example:
ViewModel
[Validator(typeof(VmSysTestValidator))]
public class VmSysTestModel
{
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
Fluent validation implementation :
public class VmSysTestValidator : AbstractValidator<VmSysTestModel>
{
public VmSysTestValidator()
{
RuleFor(x => x.FirstName).NotNull().WithMessage("First name is required");
RuleFor(x => x.LastName).NotNull().WithMessage("Last Name is required");
}
}
Controller or business logic side :
[HttpPost]
public ActionResult TestPost(VmSysTestModel obj)
{
//Start Validation From start to end you can call this code everywhere you want , this will work like server side valdiatin
//Instead of ModelState.IsValid you will call your fluent validator
var testValidator = new VmSysTestValidator();
var validationResult = testValidator.Validate(obj);
if (validationResult.IsValid)
{
}
else
{
}
//End valdiation
}
Related
I have base class for every request in my app:
public abstract class BaseDto
{
public string Uid { get; set; }
}
public class RequestDto : BaseDto
{
public string SomeData { get; set; }
}
Im using my ReuqestDto class in my controller actions:
[HttpGet]
public IEnumerable<string> Get(RequestDto req)
{
// some logic on request
if (req.Uid != null)
{
// perform action
}
}
The user passing only SomeData property to me. In my JWT Token i have saved some information about Uid for BaseDto. What is the best way to write data to Uid using middleware/filter to have that information in my Get() method? I Tried to serialized HttpContext.Request.Body but not success because i cant find, how to do it properly. Or maybe there are better solutions for this problem? How to write data to my incoming objects in app?
This is probably what you want.
You should to create own interface for models like that
public interface IMyRequestType { }
Your model should implement it for finding model in FilterAttribute
public class MyModel : IMyRequestType
{
public string ID { get; set; }
}
And create your filter attribute with OnActionExecuting implentation
public class MyFilterAttribute : TypeFilterAttribute
{
public MyFilterAttribute() : base(typeof(MyFilterImpl)) { }
private class MyFilterImpl : IActionFilter
{
private readonly ILogger _logger;
public MyFilterAttributeImpl(ILoggerFactory loggerFactory)
{
// get something from DI
_logger = loggerFactory.CreateLogger<MyFilterAttributeImpl>();
}
public void OnActionExecuting(ActionExecutingContext context)
{
// get your request model
var model = context.ActionArguments.Values.OfType<IMyRequestType>().Single();
// get your key
//context.HttpContext.User or whatever
// do something with model
}
public void OnActionExecuted(ActionExecutedContext context)
{
// perform some logic work
}
}
}
I often created a filter which implements Attribute and IAsyncActionFilter to get the information before go inside the Controller's action.
Here is an example,
using System.IdentityModel.Tokens.Jwt;
public class UserProfileFilter : Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
string uid = string.Empty;
StringValues authHeaderVal = default(StringValues);
// Get UID from JWT
if (context.HttpContext.Request.Headers.TryGetValue("Authorization", out authHeaderVal))
{
string bearerTokenPrefix = "Bearer";
string accessToken = string.Empty;
string authHeaderStr = authHeaderVal.ToString();
if (!string.IsNullOrEmpty(authHeaderStr) && authHeaderStr.StartsWith(bearerTokenPrefix, StringComparison.OrdinalIgnoreCase))
{
accessToken = authHeaderStr.Replace(bearerTokenPrefix, string.Empty, StringComparison.OrdinalIgnoreCase).Trim();
}
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(accessToken);
uid = token.Claims.FirstOrDefault(c => c.Type.Equals("sub", StringComparison.OrdinalIgnoreCase))?.Value;
}
// Or Get UID from ActionExecutingContext
var user = context.HttpContext.User;
if (user.Identity.IsAuthenticated)
{
uid = user.Claims.FirstOrDefault(c => c.Type.Equals("sub", StringComparison.OrdinalIgnoreCase))?.Value;
}
// Get payload
RequestDto payload = (RequestDto)context.ActionArguments?.Values.FirstOrDefault(v => v is RequestDto);
payload.Uid = uid;
await next();
}
}
And then you can put the filter on any action.
[HttpPost]
[Authorize]
[TypeFilter(typeof(UserProfileFilter))]
public ActionResult<IActionResult> AdminGet(RequestDto request)
{
Debug.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(request));
return this.Ok();
}
The above filter will use the sub claim's value to overwrite the value of the incoming payload.
For example, if I post the payload as following,
{
"uid" : "",
"someData": "Test"
}
The action will finally output {"Uid":"MyID","SomeData":"Test"}.
I cannot access the original value that didn't pass the model validation. I would suspect AttemptedValue and/or RawValue in ModelStateEntry to contain the original value, however both properties are null.
For clarification, I wrote a minimalistic api, to showcase the issue.
The model to validate:
public class User
{
[EmailAddress]
public string Email { get; set; }
}
The controller:
[ApiController]
[Route("test")]
public class TestController : ControllerBase
{
[HttpPost]
[ValidationFilter()]
public string Test([FromBody] User user)
{
return user.Email;
}
}
The validation filter:
public class ValidationFilterAttribute : ActionFilterAttribute, IOrderedFilter
{
public int Order { get; } = int.MinValue;
override public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
ModelStateEntry entry = context.ModelState.ElementAt(0).Value;
var attemptedVal = entry.AttemptedValue;
var rawVal = entry.RawValue;
context.Result = new OkObjectResult(rawVal);
}
}
}
When I call the test method with this model:
{
"email": "No email here ;)"
}
The ValidationFilterAttribute code is called as expected, however the ModelStateEntry does not contain the original value. Both AttemptedValue and RawValue are null:
Visual Studio debugging screenshot
As far as I know, for model binding, the filter will calls context.ModelState.SetModelValue to set value for RawValue and AttemptedValue.
But the SystemTextJsonInputFormatter doesn't set it to solve this issue, I suggest you could try to build custom extension method and try again.
More details, you could refer to below codes:
Create a new ModelStateJsonInputFormatter class:
public class ModelStateJsonInputFormatter : SystemTextJsonInputFormatter
{
public ModelStateJsonInputFormatter(ILogger<ModelStateJsonInputFormatter> logger, JsonOptions options) :
base(options ,logger)
{
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var result = await base.ReadRequestBodyAsync(context);
foreach (var property in context.ModelType.GetProperties())
{
var propValue = property.GetValue(result.Model, null);
var propAttemptValue = property.GetValue(result.Model, null)?.ToString();
context.ModelState.SetModelValue(property.Name, propValue, propAttemptValue);
}
return result;
}
}
Reigster it in startup.cs:
services.AddControllersWithViews(options => {
var serviceProvider = services.BuildServiceProvider();
var modelStateJsonInputFormatter = new ModelStateJsonInputFormatter(
serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<ModelStateJsonInputFormatter>(),
serviceProvider.GetRequiredService<IOptions<JsonOptions>>().Value);
options.InputFormatters.Insert(0, modelStateJsonInputFormatter);
});
Result:
I am having some ActionFilters in my asp.net core mvc application, which validate some input data. For example, the client sends a userId inside the header, the filter loads that user from a repository, and validates, if the user exists, is active, has a license, and so on.
This filter is attached to a Controller method. The Controller method also needs to collect the same user object. Because of performance, I want to pass that user object, collected inside the filter, to the controller, so the controller does not need to load the same user object again. I know there are ways to do so, like mentioned here.
Because of clean code, I wonder if this would be possible, coding an attribute which defines what to retrieve, like the [FromBody] attribute does, for instance.
I could imagine this attribute named [FromFilter("User")], which takes a parameter to specify the key inside the HttpContext.Items
A basic implementation could be something like this:
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromFilterAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
{
/// <inheritdoc />
public BindingSource BindingSource => BindingSource.Custom;
/// <inheritdoc />
public string Name { get; set; }
}
Neither do I know if this would a be a good idea, nor how to implement such a feature. Hopefully someone can me point into the right direction
As far as I know, we couldn't directly pass the object from filter to action.
In my opinion, the best solution is creating a custom model binding and then find the user from the repository and pass the user to the action.
Since the model binding is triggered before the filter, you could get the custom model binding result from the ActionExecutingContext.
Order of execution:
UserModelBinder --> OnActionExecuting --> Index action
More details about to do it, you could refer to below codes:
Custom model binding:
public class UserModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var model = new UserModel()
{
id = 1,
name = "test"
};
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Controller action and OnActionExecuting method:
OnActionExecuting:
public override void OnActionExecuting(ActionExecutingContext context)
{
//ActionArguments["user"] is the parameter name of the action parameter
var user = context.ActionArguments["user"] as UserModel;
// Do something before the action executes.
base.OnActionExecuting(context);
}
Action method:
public async Task<IActionResult> Index([ModelBinder(BinderType = typeof(UserModelBinder))] UserModel user)
{
int i =0;
return View();
}
Result:
Filter onexecuting:
Action parameter:
You can use HttpContext.Items for this and create HttpContextItemsModelBinder which will bind model from HttpContext.Items
public class HttpContextItemsModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var items = bindingContext.HttpContext.Items;
string name = bindingContext.BinderModelName ?? bindingContext.FieldName;
bindingContext.Result = items.TryGetValue(name, out object item)
? ModelBindingResult.Success(item)
: ModelBindingResult.Failed();
return Task.CompletedTask;
}
}
Create and register model binder provider
public static class CustomBindingSources
{
public static BindingSource HttpContextItems { get; } = new BindingSource("HttpContextItems", "HttpContext Items", true, true);
}
public class HttpContextItemsModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.BindingInfo.BindingSource == CustomBindingSources.HttpContextItems)
{
return new HttpContextItemsModelBinder();
}
return null;
}
}
In Startup.cs
services
.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new HttpContextItemsModelBinderProvider());
//...
})
Create an attribute which will set correct BindingSource to use HttpContextItemsModelBinder
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromHttpContextItemsAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
{
public string Name { get; set; }
public BindingSource BindingSource => CustomBindingSources.HttpContextItems;
public FromHttpContextItemsAttribute(string name)
{
Name = name;
}
public FromHttpContextItemsAttribute() { }
}
Usage:
//in controller
[HttpGet]
[ValidateUserFilter]
public IActionResult TestHttpContextItems([FromHttpContextItems("UserItem")]UserItemModel model)
{
return Ok(model);
}
//your action filter
public class ValidateUserFilterAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
//...
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var model = new UserItemModel
{
Id = 45,
Name = "Some user name"
};
context.HttpContext.Items["UserItem"] = model;
}
}
Important note
Pay attention that I save user model to HttpContext.Items during OnAuthorization and not OnActionExecuting because model binding happens before any action filters run, so HttpContext.Items won't contain user and model binding will fail. You might need to adjust filter code to your needs and to make the solution work as expected.
Usage without specifying item name. Parameter name in action method should match key ("userModel") used to store value in HttpContext.Items:
//in controller
[HttpGet]
[ValidateUserFilter]
public IActionResult TestHttpContextItems([FromHttpContextItems]UserItemModel userModel)
{
return Ok(userModel);
}
//action filter
public class ValidateUserFilterAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
//...
}
public void OnAuthorization(AuthorizationFilterContext context)
{
//...
context.HttpContext.Items["userModel"] = model;
}
}
I have created a generic interface
public interface ISqlTradeDataRetriever<T> where T : class
{
Task<T> GetSingleDayForSymbolAsync(string symbol, string date);
Task<ICollection<T>> GetAllAsync(string symbol);
}
and an implementation of such
public class SqlCommodityDataRetriever: ISqlTradeDataRetriever<Commodity>
{
private readonly BatlGroupWebContext _context;
public SqlCommodityDataRetriever(BatlGroupWebContext context)
{
_context = context;
}
public Task<Commodity> GetSingleDayForSymbolAsync(string symbol, string date)
{
var data = _context.Commodities.FirstOrDefaultAsync(m => m.Symbol == symbol
&& m.TradeDate == Convert.ToDateTime(date));
return data;
}
public Task<ICollection<Commodity>> GetAllAsync(string symbol)
{
throw new NotImplementedException();
}
}
and in startup.cs of the web application
services.AddScoped<ISqlTradeDataRetriever<Commodity>, SqlCommodityDataRetriever>();
but when I try to access the page where I am using this implementation I get an unresolved DI error
Unable to resolve service for type 'Infrastructure.DataRetrievers.SqlCommodityDataRetriever' while attempting to activate 'BATLGroupApp.Pages.Commodity.TradeData.EditModel'.
The edit model is as follows
public class EditModel : PageModel
{
private readonly SqlCommodityDataRetriever _retriever;
public EditModel(SqlCommodityDataRetriever retriever)
{
_retriever = retriever;
}
[BindProperty]
public DomainClasses.Classes.Commodity Commodity { get; set; }
public string CommoditySymbol { get; set; }
public async Task<IActionResult> OnGetAsync([FromQuery]string symbol, [FromQuery]string date)
{
if (string.IsNullOrEmpty(symbol) || string.IsNullOrEmpty(date))
{
return NotFound();
}
Commodity = await _retriever.GetSingleDayForSymbolAsync(symbol, date);
if (Commodity == null)
{
return NotFound();
}
CommoditySymbol = Commodity.CommoditySymbol.Symbol;
return Page();
}
}
What am I doing wrong in terms of registering the DI for this OR is my implementation wrong?
The registration is fine in its current form.
services.AddScoped<ISqlTradeDataRetriever<Commodity>, SqlCommodityDataRetriever>();
The page model however expects the concrete SqlCommodityDataRetriever implementation while the container is only aware of its ISqlTradeDataRetriever<Commodity> abstraction.
Refactor the page model to match the registration
public class EditModel : PageModel {
private readonly ISqlTradeDataRetriever<Commodity> _retriever;
public EditModel(ISqlTradeDataRetriever<Commodity> retriever) {
_retriever = retriever;
}
//... omitted for brevity since nothing else needs to change
as the class should ideally be dependent on abstractions instead of concretions for a more SOLID design.
I have two controllers:
public class AController : Controller
{
public ActionResult AControllerAction()
{
if (// BControllerAction reported an error somehow )
{
ModelState.AddModelError("error-key", "error-value");
}
...
}
}
public class BController : Controller
{
public ActionResult BControllerAction()
{
try{Something();}
catch(SomethingExceprion)
{
// here I need to add error info data,
// pass it to AController and redirect to
// AControllerAction where this error will be added
// to model state
}
}
}
I think I can do something like:
public ActionResult BControllerAction()
{
try{Something();}
catch(SomethingException)
{
var controller = new AController();
controller.ModelState.AddModelError("error-key", "error-value");
controller.AControllerAction();
}
}
But I suggest it will be architecture breaking approach, and I don't want to do like that. Is there some simpler and safer way, except passing model object?
Depending on what details of the exception you need to pass back to Controller A, I would do something along the lines of
public ActionResult BControllerAction()
{
try{Something();}
catch(SomethingException ex)
{
return RedirectToAction("AControllerAction", "AController", new { errorMessage = ex.Message() })
}
}
And then change the signature of the called method to
public ActionResult AControllerAction(string errorMessage)
{
if (!String.IsNullOrEmpty(errorMessage))
{
//do something with the message
}
...
}
You can return a redirect to AControllerAction. You can use the TempData dictionary (similar to ViewData) to share data across such a call (data stored this way will persist to the next request in the same session, as explained in this blog post).
Example:
public class AController : Controller
{
public ActionResult AControllerAction()
{
if (TempData["BError"] != null)
{
ModelState.AddModelError("error-key", "error-value");
}
...
}
}
public class BController : Controller
{
public ActionResult BControllerAction()
{
try{Something();}
catch(SomethingExceprion)
{
TempData["BError"] = true;
return RedircetToAction("AControllerAction", "AController");
}
}
}