<cite>ismcagdas: </cite> Hi,
ABP actually calls CheckModelState before calling the action method of controller by default. You can add [DisableValidation] attribute to your controller or action method, if you want to do validation manually.
You can also disable validation for all MVC controllers like this in PreInitialize of your web module.
Configuration.Modules.AbpMvc().IsValidationEnabledForControllers = false;
Thank you, that's good to know.
In the Unit Testing tutorial, it is mentioned that we use real implementations of dependencies when unit testing. What if we do not want to do this? For instance, I have an application service method that queues a background job (using the default background job manager). When I am unit testing this method, I do not want the job to actually be queued (in this case, I am queuing a job to send out an email). I only want to verify that JobManager.EnqueueAsync is called by my service method. I tried playing around with Moq and Castle to get it to work but my JobManager is always the real implementation of it. I could always stub out the call to JobManager.EnqueueAsync, and set a flag that I can check to verify it has been called but then I'm not sure how to do that when its recommended to use Resolve <IMyApplicationService>() to obtain a reference to my application service. I appreciate any advice!
<cite>hikalkan: </cite> Hi @difuzzy,
What do you mean by clients of Web API? Browsers, mobile apps.. which calls Web API services remotely. If so, no problem, token provider will be valid since it's assigned in Web/WebApi layer, in Startup class: <a class="postlink" href="https://github.com/aspnetzero/aspnet-zero/blob/dev/src/MyCompanyName.AbpZeroTemplate.Web/App_Start/Startup.cs#L43">https://github.com/aspnetzero/aspnet-ze ... tup.cs#L43</a>
Beside that, if you worry about "good layering", you're right. Because, if you develop a Desktop application and want to use application layer (and don't use web/webapi layer), then tokenprovider will not be defined and you will get exception if you generate password reset token using UserManager.GeneratePasswordResetTokenAsync. That means, a functionality in application layer, indirectly depends on the web layer and it has not a built-in fallback mechanism (to allow work without web layer).
Why we did it like that? Because this is the most common way of getting a valid data protection validator, OWIN provides it to us. We haven't searched for another solution.
So, it's actually not a problem for you, but if you care about it, please create an issue here and we search for a better way: <a class="postlink" href="https://github.com/aspnetboilerplate/module-zero/issues/new">https://github.com/aspnetboilerplate/mo ... issues/new</a>
Thanks a lot.
No that all makes sense. You're right, I should be able to generate a token in WebAPI. I should be good to go. Thanks!
<cite>ismcagdas: </cite> @djfuzzy, I think you can gneerate tokens in the application layer, because you can use UserManager in application layer. But you need to set userManager's UserTokenProvider in web layer (probably in your web module).
How would this work for clients of the Web API? It sounds like having the Application layer being dependent on the Web layer defies the SOLID principles of programming. I don't want to have each client having to generate the token.
Is it possible to have this logic in the Application layer? I would much rather have the email confirmation tokens being generated there then having clients do it.
OK, I got it working! Sorry for the late reply. Thanks for your help.
Yes, I have used the template. I am running ABP v0.10.3. I also read the documentation and have set CustomErrors to "On". I am still getting an Internal Server Error with the stack trace I posted above. I still am unable to figure out how to show the specific validation error on the calling form instead of the unhandled exception as shown above. I don't think my DTOs are being automatically validated as I have the call to the application service that is failing in a try/catch in my MVC controller. I would expect to get a friendly error saying Nickname is required instead of the internal server error. Here is my action method in my MVC controller:
[HttpPost]
[UnitOfWork]
public virtual async Task<ActionResult> SignUp(SignUpViewModel model)
{
try
{
CheckModelState();
var input = new CreatePlayerWithNewUserInput()
{
User = new CreateUserInput
{
Nickname = model.Nickname,
EmailAddress = model.EmailAddress,
PasswordHashed = new PasswordHasher().HashPassword(model.Password),
IsActive = true
}
};
//Save user
await PlayerAppService.CreatePlayerWithNewUser(input);
await UnitOfWorkManager.Current.SaveChangesAsync();
//Directly login if possible
if (input.User.IsActive)
{
AbpUserManager<Tenant, Role, User>.AbpLoginResult loginResult;
loginResult = await GetLoginResultAsync(input.User.EmailAddress, model.Password);
if (loginResult.Result == AbpLoginResultType.Success)
{
await SignInAsync(loginResult.User, loginResult.Identity);
return Redirect(Url.Action("Index", "Home"));
}
Logger.Warn("New registered user could not be login. This should not be normally. login result: "
+ loginResult.Result);
}
//If can not login, show a register result page
return View("SignUpResult", new SignUpResultViewModel
{
Nickname = input.User.Nickname,
EmailAddress = input.User.EmailAddress,
IsActive = input.User.IsActive
});
}
catch (UserFriendlyException ex)
{
ViewBag.ErrorMessage = ex.Message;
ViewBag.ErrorDetails = ex.Details;
return SignUpView(model);
}
catch (Exception ex)
{
ViewBag.ErrorMessage = ex.Message;
return SignUpView(model);
}
}
Another thing that isn't working is when I have validation annotations in SignUpViewModel. I get a similar server error before the MVC controller action is even called. Something is not working.
I have a DTO that includes validation including some custom validation, but it is not being caught and thrown. I thought these should be thrown automatically. Here is my DTO:
public class CreateUserInput : ICustomValidate
{
[Required]
[StringLength(User.MaxNicknameLength, MinimumLength = User.MinNicknameLength)]
public string Nickname { get; set; }
[Required]
[EmailAddress]
[StringLength(AbpUserBase.MaxEmailAddressLength)]
public string EmailAddress { get; set; }
[Required]
[DisableAuditing]
public string PasswordHashed { get; set; }
public bool IsActive { get; set; }
public void AddValidationErrors(List<ValidationResult> results)
{
if (Regex.IsMatch(Nickname, User.NicknameFormatValidationRegex))
{
throw new UserFriendlyException(string.Format("Nickname must be between {0} and {1} characters, may " +
"only contain letters, numbers, spaces, hyphens, underscores and periods, and cannot begin or end " +
"with spaces.", User.MinNicknameLength, User.MaxNicknameLength));
}
}
}
I have a view model that has some validation attributes:
public class SignUpViewModel
{
[Required]
[StringLength(User.MaxNicknameLength, MinimumLength = User.MinNicknameLength)]
public string Nickname { get; set; }
[Required]
[EmailAddress]
[StringLength(User.MaxEmailAddressLength)]
public string EmailAddress { get; set; }
[StringLength(User.MaxPlainPasswordLength, MinimumLength = User.MinPlainPasswordLength)]
[DisableAuditing]
public string Password { get; set; }
public bool IsExternalLogin { get; set; }
}
However, if validation fails, before the controller action method is called, which would call the CheckModelState method, an exception is thrown:
Server Error in '/' Application.
Method arguments are not valid! See ValidationErrors for details.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: Abp.Runtime.Validation.AbpValidationException: Method arguments are not valid! See ValidationErrors for details.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[AbpValidationException: Method arguments are not valid! See ValidationErrors for details.] Abp.Runtime.Validation.Interception.MethodInvocationValidator.Validate() in D:\Halil\GitHub\aspnetboilerplate\src\Abp\Runtime\Validation\Interception\MethodInvocationValidator.cs:92 Abp.Web.Mvc.Validation.AbpMvcValidationFilter.OnActionExecuting(ActionExecutingContext filterContext) in D:\Halil\GitHub\aspnetboilerplate\src\Abp.Web.Mvc\Web\Mvc\Validation\AbpMvcValidationFilter.cs:35 System.Web.Mvc.Async.AsyncInvocationWithFilters.InvokeActionMethodFilterAsynchronouslyRecursive(Int32 filterIndex) +176 System.Web.Mvc.Async.AsyncInvocationWithFilters.InvokeActionMethodFilterAsynchronouslyRecursive(Int32 filterIndex) +644 System.Web.Mvc.Async.AsyncInvocationWithFilters.InvokeActionMethodFilterAsynchronouslyRecursive(Int32 filterIndex) +644 System.Web.Mvc.Async.<>c__DisplayClass33.<BeginInvokeActionMethodWithFilters>b__31(AsyncCallback asyncCallback, Object asyncState) +58 System.Web.Mvc.Async.WrappedAsyncResult
1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +14 System.Web.Mvc.Async.WrappedAsyncResultBase
1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128 System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeActionMethodWithFilters(ControllerContext controllerContext, IList1 filters, ActionDescriptor actionDescriptor, IDictionary
2 parameters, AsyncCallback callback, Object state) +197 System.Web.Mvc.Async.<>c__DisplayClass21.<BeginInvokeAction>b__19(AsyncCallback asyncCallback, Object asyncState) +743 System.Web.Mvc.Async.WrappedAsyncResult1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +14 System.Web.Mvc.Async.WrappedAsyncResultBase
1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128 System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state) +343 System.Web.Mvc.Controller.<BeginExecuteCore>b__1c(AsyncCallback asyncCallback, Object asyncState, ExecuteCoreState innerState) +25 System.Web.Mvc.Async.WrappedAsyncVoid1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +30 System.Web.Mvc.Async.WrappedAsyncResultBase
1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128 System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state) +465 Castle.Proxies.AccountControllerProxy.BeginExecuteCore_callback(AsyncCallback callback, Object state) +8 Castle.Proxies.Invocations.Controller_BeginExecuteCore.InvokeMethodOnTarget() +80 Castle.DynamicProxy.AbstractInvocation.Proceed() +80 Abp.Domain.Uow.UnitOfWorkInterceptor.Intercept(IInvocation invocation) in D:\Halil\GitHub\aspnetboilerplate\src\Abp\Domain\Uow\UnitOfWorkInterceptor.cs:29 Castle.DynamicProxy.AbstractInvocation.Proceed() +108 Castle.Proxies.AccountControllerProxy.BeginExecuteCore(AsyncCallback callback, Object state) +160 System.Web.Mvc.Controller.<BeginExecute>b__14(AsyncCallback asyncCallback, Object callbackState, Controller controller) +18 System.Web.Mvc.Async.WrappedAsyncVoid1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +20 System.Web.Mvc.Async.WrappedAsyncResultBase
1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128 System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +374 Castle.Proxies.AccountControllerProxy.BeginExecute_callback(RequestContext requestContext, AsyncCallback callback, Object state) +12 Castle.Proxies.Invocations.Controller_BeginExecute.InvokeMethodOnTarget() +131 Castle.DynamicProxy.AbstractInvocation.Proceed() +80 Abp.Domain.Uow.UnitOfWorkInterceptor.Intercept(IInvocation invocation) in D:\Halil\GitHub\aspnetboilerplate\src\Abp\Domain\Uow\UnitOfWorkInterceptor.cs:29 Castle.DynamicProxy.AbstractInvocation.Proceed() +108 Castle.Proxies.AccountControllerProxy.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +174 System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +16 System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__4(AsyncCallback asyncCallback, Object asyncState, ProcessRequestState innerState) +52 System.Web.Mvc.Async.WrappedAsyncVoid1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +30 System.Web.Mvc.Async.WrappedAsyncResultBase
1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128 System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +384 System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +48 System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +16 System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +103 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.6.1586.0
I'm confused why validation is happening before the controller action method is called and how can I catch this exception to display a user friendly error instead?
Hi, yes I have read the documentation but there doesn't seem to be anything in there about the use of localization in models. Here is an example model where I am trying to do this:
public class CreateUserInput : ICustomValidate
{
[Required]
[StringLength(User.MaxNicknameLength, MinimumLength = User.MinNicknameLength)]
public string Nickname { get; set; }
[Required]
[EmailAddress]
[StringLength(AbpUserBase.MaxEmailAddressLength)]
public string EmailAddress { get; set; }
[Required]
[DisableAuditing]
public string PasswordHashed { get; set; }
public bool IsActive { get; set; }
public void AddValidationErrors(List<ValidationResult> results)
{
if (Regex.IsMatch(Nickname, User.NicknameFormatValidationRegex))
{
throw new UserFriendlyException(string.Format("Nickname must be between {0} and {1} characters, may " +
"only contain letters, numbers, spaces, hyphens, underscores and periods, and cannot begin or end " +
"with spaces.", User.MinNicknameLength, User.MaxNicknameLength));
}
}
I would like the UserFriendlyException content in the AddValidation method to be something like:
L("NicknameInvalidFormat", User.MinNicknameLength, User.MaxNicknameLength)