I’m developing a web application using Razor Pages and Code First.
I know that in ASP.NET MVC, you can use Remote above a property referring to an action in a controller that validates the data without the whole page being posted back. But it doesn’t seem to work in Razor Pages as there’s no Controller and Action in ASP.NET Core Razor Pages.
So, How can I get remote validation done in Razor Pages?
For anyone like me who finds this later and loses their mind trying to pass a property from their model onto the validation method, make the method signature look like so
public IActionResult IsCharacterNameAvailable([Bind(Prefix = "Character.Name")] string name)
Character is the model and Name is the property. Without adding the [Bind(Prefix = "")] before the parameter I was always receiving a null value. Hope this helps!
I added the following in my model class:
[Remote(action: "IsNationalIdValid",controller:"Validations")]
I created 'Controllers' folder in my Razor Pages project and added a controller(ValidationsController) with the following method:
public IActionResult IsNationalIdValid(string nationalId){}
However,when I tried to go to the page where this validation was supposed to work,I got the following exception:
No URL for remote validation could be found in asp.net core
Thanks to a reply to the same thread in Asp.Net forum,I figured out the answer:
All I needed to do was to add the following code in Startup.cs file of my Razor Pages project in order to configure the route.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Hope this answer will help someone else as well.
In the base class that RemoteAttribute derives from there's a protected GetUrl() method than can be overriden. Therefore I created my own MyRemoteAttribute class
public class MyRemoteAttribute : RemoteAttribute
{
/// <summary>
/// Initialise an instance of the <see cref="MyRemoteAttribute"/>
/// </summary>
/// <param name="handler">The name of the Razor Page Handler</param>
/// <param name="page">The Razor Page name</param>
public MyRemoteAttribute(string handler = null, string page = null)
{
Handler = handler;
Page = page;
}
/// <summary>
/// Gets/sets the url to use for remote validation
/// </summary>
public string Url { get; set; }
public string Page { get; private set; }
public string Handler { get; private set; }
protected override string GetUrl(ClientModelValidationContext context)
{
// Use an URL is specified
if (!string.IsNullOrEmpty(Url)) return Url;
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (string.IsNullOrEmpty(Handler))
{
throw new InvalidOperationException("No Handler specified");
}
var services = context.ActionContext.HttpContext.RequestServices;
var factory = services.GetRequiredService<Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory>();
var urlHelper = factory.GetUrlHelper(context.ActionContext);
var page = Page?? context.ActionContext.RouteData.Values["page"] as string;
Url = urlHelper.Page(page, Handler);
if (Url == null)
{
throw new InvalidOperationException();
}
return Url;
}
}
In my app which is using areas, creates a url /BusinessPartners/Clients/Create?handler=CheckUnique
To use decorate your model property with [MyRemote(Url="/Something/Somecheck")] to use the specified URL, or [MyRemote("CheckUnique")] to specify the Razor Page Handler. The handler should be named OnGet<handlername> and needs to return a JsonResult with true for passing validation, false or null if validation fails.
The handler in the Razor Page is:
public IActionResult OnGetCheckUnique(string shortName)
{
var found = db.Queryable<BusinessPartner>().Any(a => a.ShortName == shortName);
return new JsonResult(!found);
}
This is the same as you would do for the RemoteAttribute with the exception on the slightly modified naming convention.
I like my validation to be close to the point where it is used so therefore I've put it in the same page. I also have use a single [BindProperty] for a model class just to keep things neat and manageable.
Looks like there is a feature request for remote validation in ASP.NET Core Razor Pages but it is not priority:
https://github.com/aspnet/Mvc/issues/8245
The PageRemoteValidation attribute was introduced in ASP.NET Core 3.0 and is designed specifically to work with a Razor Pages handler method.
So, if you are working with ASP.NET Core 2.x, or your validation endpoint is an MVC controller, you must use the RemoteValidation attribute. If you are working with ASP.NET Core 3.x or newer, AND your validation service is a Razor Pages handler method, you must use the PageRemoteValidation attribute.
Here is an example describing this in details:
Related
I'm writing an app using WebAssembly Blazor hosted by ASP.NET Core. Some of pages are implemented in Blazor, but some old pages are still ASP.NET Core Razor views. I need to create a link in Blazor component pointing to action of controller on server.
I can write:
NavigationManager.NavigateTo("SomeContoller/SomeAction/123", true)
But I don't want to hardcode url to action, because changing server routing or contoller/action names will break such links. Is there any way to create proper links via some helper, similar to ASP.Net Core UriHelper? Like:
UriHelper.Action("SomeAction", "SomeController", new {id = 123});
In Blazor server apps you can use LinkGenerator. The usage is not much different that of UriHelper:
#using Microsoft.AspNetCore.Routing
#inject LinkGenerator LinkGenerator
Sign in
ReSharper understands this one too, so you will get auto-completion for controller and action names.
In WebAssembly apps LinkGenerator is not available, so your best bet is to dump all routes from the server and implement your own link generator which uses that data on the client (its complexity depends on complexity of your routes, the one from ASP.NET Core is quite complex).
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Routing;
namespace BlazorTest.Server.Controllers
{
[Route("api/routes")]
[ApiController]
public class RouteInformationController : ControllerBase
{
private readonly EndpointDataSource _endpointDataSource;
public RouteInformationController(EndpointDataSource endpointDataSource)
{
_endpointDataSource = endpointDataSource;
}
public IEnumerable<object> Get()
{
foreach (var endpoint in _endpointDataSource.Endpoints.OfType<RouteEndpoint>())
{
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
if (actionDescriptor == null)
continue;
yield return new
{
actionDescriptor.ControllerName,
actionDescriptor.ActionName,
Parameters = actionDescriptor.Parameters.Select(p => p.Name),
RoutePattern = endpoint.RoutePattern.RawText,
};
}
}
}
}
You can create a static class with constant properties in it for all the URLs that you use in the app. After that use the same static class property in both the page route and your navigation route. Below is a very basic version of this:
public static class RouteUrls
{
public static string Home = "/Home";
public static string ProductList = "/Product";
public static string ProductDetail = "/Product/Detail";
public static string SomePage = "/SomeContoller/SomeAction";
}
// to access it use like this:
NavigationManager.NavigateTo($"{RouteUrls.SomePage}/123", true)
Upgrading to asp.net core 2.2 in my hobby project there is a new routing system I want to migrate to. Previously I implemented a custom IRouter to be able to set the controller for the request dynamically. The incoming request path can be anything. I match the request against a database table containing slugs and it looks up the a matching data container class type for the resolved slug. After that I resolve a controller type that can handle the request and set the RouteData values to the current HttpContext and passing it along to the default implementation for IRouter and everything works ok.
Custom implementaion of IRouter:
public async Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
var page = _pIndex.GetPage(requestPath);
if (page != null)
{
var controllerType = _controllerResolver.GetController(page.PageType);
if (controllerType != null)
{
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
newRouteData.Values["pageType"] = page.PageType;
newRouteData.Values["controller"] = controllerType.Name.Replace("Controller", "");
newRouteData.Values["action"] = "Index";
context.RouteData = newRouteData;
await _defaultRouter.RouteAsync(context);
}
}
}
A controller to handle a specific page type.
public class SomePageController : PageController<PageData>
{
public ActionResult Index(PageData currentPage)
{
return View("Index", currentPage);
}
}
However I got stuck when I'm trying to figure out how I can solve it using the new system. I'm not sure where I'm suppose to extend it for this behavior. I don't want to turn off the endpoint routing feature because I see an opportunity to learn something. I would aso appreciate a code sample if possible.
In ASP.NET 3.0 there is an new dynamic controller routing system. You can implement DynamicRouteValueTransformer.
Documentation is on the way, look at the github issue
I have an action in my ASP.Net Core WebAPI Controller which takes one parameter. I'm trying to configure it to be able to call it in following forms:
api/{controller}/{action}/{id}
api/{controller}/{action}?id={id}
I can't seem to get the routing right, as I can only make one form to be recognized. The (simplified) action signature looks like this: public ActionResult<string> Get(Guid id). These are the routes I've tried:
[HttpGet("Get")] -- mapped to api/MyController/Get?id=...
[HttpGet("Get/{id}")] -- mapped to api/MyController/Get/...
both of them -- mapped to api/MyController/Get/...
How can I configure my action to be called using both URL forms?
if you want to use route templates
you can provide one in Startup.cs Configure Method Like This:
app.UseMvc(o =>
{
o.MapRoute("main", "{controller}/{action}/{id?}");
});
now you can use both of request addresses.
If you want to use the attribute routing you can use the same way:
[HttpGet("Get/{id?}")]
public async ValueTask<IActionResult> Get(
Guid id)
{
return Ok(id);
}
Make the parameter optional
[Route("api/MyController")]
public class MyController: Controller {
//GET api/MyController/Get
//GET api/MyController/Get/{285A477F-22A7-4691-AA51-08247FB93F7E}
//GET api/MyController/Get?id={285A477F-22A7-4691-AA51-08247FB93F7E}
[HttpGet("Get/{id:guid?}"
public ActionResult<string> Get(Guid? id) {
if(id == null)
return BadRequest();
//...
}
}
This however means that you would need to do some validation of the parameter in the action to account for the fact that it can be passed in as null because of the action being able to accept api/MyController/Get on its own.
Reference Routing to controller actions in ASP.NET Core
I'm starting a new ASP.NET project after a few years developing in MVC4, and I have a question regarding architecture.
At the top corner of each page, I will display details of the current logged in user.
In MVC4 I achieved something like this by creating a BaseController, which created an EF data connection, and set up some common variables that would be used on every page - CurrentUser being one of them.
Now that I'm using Core, this approach doesn't seem to work, and certainly isnt mockable.
What would be the correct way to achieve something like this via ASP.NET Core?
I need the same variables on every view, and certainly dont want to have to write the code in each controller action!
You can use View Components feature in asp.net core to implement that functionality.
//In your ConfigureServices method , add your services that will be injected whenever view component is instantiated
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton<IUserRespository, UserRepository>();
}
//Now Create a view component
public class LoggedInUser : ViewComponent
{
private IUserRespository userRepository;
//Services can be injected using asp.net core DI container
public LoggedInUser(IUserRepository userRepository,SomeOtherService service)
{
//assign services to local variable for use later
this.userRepository = userRepository;
}
//This method can take any number of parameters and returns view
public async Task<IViewComponentResult> InvokeAsync(int param1,string param2,etc )
{
//get the logged in user data here using available services
var loggedInUserData = GetSomeData(context);
return View(loggedInUserData );
}
}
Create view file # View/Shared/Components/LoggedInUser/Default.cshtml.View can be strongly typed.
#model LoggedInUserModel
<div>
<!-- html here to render model -->
</div>
Now, since you use to display this data on every page , you need to apply _Layout.chstml to all your pages . In the _Layout.chstml , you can render view component defined above with any additional parameter you would like to pass as anonymous type.
#await Component.InvokeAsync("LoggedInUser", new { param1=value,param2=value,etc })
Testing the View Component:
var mockRepository = Mock of ICityRepository;
var viewComponent= new LoggedInUser(mockRepository);
ViewViewComponentResult result
= viewComponent.Invoke() as ViewViewComponentResult; //using Invoke here instead of InvokeAsnyc for simplicity
//Add your assertions now on result
Note :
It is also possible to decorate a controller with [ViewComponent(Name = "ComponentName")] attribute and define public IViewComponentResult Invoke()
or public IViewComponentResult InvokeAsync() to turn them in to hybrid controller - view component.
I am working on a multi lingual website using Umbraco 7.2.4 (.NET MVC 4.5). I have pages for each language nested under home nodes with their own culture:
Home (language selection)
nl-BE
some page
some other page
my form page
fr-BE
some page
some other page
my form page
The form model is decorated with validation attributes that I needed to translate for each language. I found a Github project, Umbraco Validation Attributes that extends decoration attributes to retrieve validation messages from Umbraco dictionary items. It works fine for page content but not validation messages.
The issue
land on nl-BE/form
field labels are shown in dutch (nl-BE)
submit invalid form
validation messages are shown in dutch (nl-BE culture)
browse to fr-BE/form
field labels are shown in french (fr-BE)
submit invalid form
Expected behavior is: validation messages are shown in french (fr-BE culture)
Actual behavior is: messages are still shown in dutch (data-val-required attribute is in dutch in the source of the page)
Investigation to date
This is not a browser cache issue, it is reproducible across separate browsers, even separate computers: whoever is generating the form for the first time will lock the validation message culture. The only way to change the language of the validation messages is to recycle the Application Pool.
I doubt that the Umbraco Validation helper class is the issue here but I'm out of ideas, so any insight is appreciated.
Source code
Model
public class MyFormViewModel : RenderModel
{
public class PersonalDetails
{
[UmbracoDisplayName("FORMS_FIRST_NAME")]
[UmbracoRequired("FORMS_FIELD_REQUIRED_ERROR")]
public String FirstName { get; set; }
}
}
View
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage
var model = new MyFormViewModel();
using (Html.BeginUmbracoForm<MyFormController>("SubmitMyForm", null, new {id = "my-form"}))
{
<h3>#LanguageHelper.GetDictionaryItem("FORMS_HEADER_PERSONAL_DETAILS")</h3>
<div class="field-wrapper">
#Html.LabelFor(m => model.PersonalDetails.FirstName)
<div class="input-wrapper">
#Html.TextBoxFor(m => model.PersonalDetails.FirstName)
#Html.ValidationMessageFor(m => model.PersonalDetails.FirstName)
</div>
</div>
note: I have used the native MVC Html.BeginForm method as well, same results.
Controller
public ActionResult SubmitFranchiseApplication(FranchiseFormViewModel viewModel)
{
if (!ModelState.IsValid)
{
TempData["Message"] = LanguageHelper.GetDictionaryItem("FORMS_VALIDATION_FAILED_MESSAGE");
foreach (ModelState modelState in ViewData.ModelState.Values)
{
foreach (ModelError error in modelState.Errors)
{
TempData["Message"] += "<br/>" + error.ErrorMessage;
}
}
return RedirectToCurrentUmbracoPage();
}
}
LanguageHelper
public class LanguageHelper
{
public static string CurrentCulture
{
get
{
return UmbracoContext.Current.PublishedContentRequest.Culture.ToString();
// I also tried using the thread culture
return System.Threading.Thread.CurrentThread.CurrentCulture.ToString();
}
}
public static string GetDictionaryItem(string key)
{
var value = library.GetDictionaryItem(key);
return string.IsNullOrEmpty(value) ? key : value;
}
}
So I finally found a workaround. In attempt to reduce my app to its simplest form and debug it, I ended up recreating the "UmbracoRequired" decoration attribute. The issue appeared when ErrorMessage was set in the Constructor rather than in the GetValidationRules method. It seems that MVC is caching the result of the constructor rather than invoking it again every time the form is loaded. Adding a dynamic property to the UmbracoRequired class for ErrorMessage also works.
Here's how my custom class looks like in the end.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,
AllowMultiple = false)]
internal class LocalisedRequiredAttribute : RequiredAttribute, IClientValidatable
{
private string _dictionaryKey;
public LocalisedRequiredAttribute(string dictionaryKey)
{
_dictionaryKey = dictionaryKey;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata, ControllerContext context)
{
ErrorMessage = LanguageHelper.GetDictionaryItem(_dictionaryKey); // this needs to be set here in order to refresh the translation every time
yield return new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage, // if you invoke the LanguageHelper here, the result gets cached and you're locked to the current language
ValidationType = "required"
};
}
}