I'm trying to develop a custom component that will need to call a method from the backingbean to get some data from the bb (this will be called in the decode phase after a certain Ajax call) with one parameter (it will come in the ajax call).
The problem I'm having is that I define the attribute as a MethodExpression (in the taglibrary and the component), I get the Ajax post, decode the parameter and when I try to get the Method binding from the component I get the following error:
javax.el.PropertyNotFoundException: /easyFaces.xhtml #19,151
dataSource="#{theBean.loadDataFromSource}": The class
'ar.com.easytech.faces.test.homeBean' does not have the property
'loadDataFromBean'.
Here is the relevant code.. (and please let me know if this is not the correct way to do this..)
taglib:
<attribute>
<display-name>Data Source</display-name>
<name>dataSource</name>
<required>true</required>
<type>javax.el.MethodExpression</type>
<method-signature>java.util.List theDataSource(java.lang.String)</method-signature>
</attribute>
Component definition:
public class Autocomplete extends HtmlInputText implements ClientBehaviorHolder
...
public MethodExpression getDataSource() {
return (MethodExpression) getStateHelper().eval(PropertyKeys.dataSource);
}
public void setDataSource(MethodExpression dataSource) {
getStateHelper().put(PropertyKeys.dataSource, dataSource);
}
and finally the rendered method that generates the error:
private List<Object> getData(FacesContext context, Autocomplete autocomplete, String data) {
Object dataObject = null;
MethodExpression dataSource = autocomplete.getDataSource();
if (dataSource != null) {
try {
dataObject = dataSource.invoke(context.getELContext(), new Object[] {data});
return convertToList(dataObject);
} catch (MethodNotFoundException e) {
logger.log(Level.INFO,"Method not found: {0}", dataSource.getExpressionString() );
}
}
return null;
}
Here is the method from the BB
public List<String> autcompleteFromSource(String param) {
List<String> tmpData = new ArrayList<String>();
tmpData.add("XXA_TABLE_A");
tmpData.add("XXA_TABLE_B");
tmpData.add("XXA_TABLE_C");
return tmpData;
}
And the .xhtml with the component
<et:autocomplete id="autoc" minLength="3" delay="500" value="#{easyfacesBean.selectedValue}" dataSource="#{easyfacesBean.autcompleteFromSource}" />
The thing is if I define a method getAutocompleteFromSource() it recognised the method and the error changes to can't convert list to MethodExpression, so evidently it is simply interpreting the autocompleteFromSource as a simple property and not a method definition, is this even the correct way to call method from BB? (giving that it's not an actual action nor validation )
I found the solution for this, as it turns out you also need to define a "Handler"to define the Method Signature, so I created the handler and added to the taglib and everything started to work fine..just for reference.. here is the handler..
Regards
public class AutocompleteHandler extends ComponentHandler {
public AutocompleteHandler(ComponentConfig config) {
super(config);
}
protected MetaRuleset createMetaRuleset(Class type) {
MetaRuleset metaRuleset = super.createMetaRuleset(type);
metaRuleset.addRule(new MethodRule("dataSource", List.class, new Class[] { String.class }));
return metaRuleset;
}
}
Related
I am using IStringLocalizer approach to localize my Blazor app as discussed here.
Injecting the IStringLocalizer on razor pages works great. I also need this to localize some services - whether scoped or even singleton services.
Using constructor injection to inject my IStringLocalizer service into the service works. However, when users change the language via UI, the service (whether singleton or scoped) keeps the initial IStringLocalizer - i.e. the one with the original language used when starting the app, not the updated language selected by the user.
What is the suggested approach to retrieve the updated IStringLocalizer from code?
EDIT
To prevent more details, here is some piece of code.
First, I add a Resources folder and create there a default LocaleResources.resx (with public modifiers) and a LocaleResources.fr.resx file, which contain the key-value pairs for each language.
Supported cultures are defined in the appsettings.json file as
"Cultures": {
"en-US": "English",
"fr": "Français (Suisse)",
...
}
In startup, I register the Resources folder and the supported cultures :
public void ConfigureServices(IServiceCollection services {
...
services.AddLocalization(options => options.ResourcesPath = "Resources");
...
services.AddSingleton<MySingletonService>();
services.AddScoped<MyScopedService>();
}
// --- helper method to retrieve the Cultures from appsettings.json
protected RequestLocalizationOptions GetLocalizationOptions() {
var cultures = Configuration.GetSection("Cultures")
.GetChildren().ToDictionary(x => x.Key, x => x.Value);
var supportedCultures = cultures.Keys.ToArray();
var localizationOptions = new RequestLocalizationOptions()
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
return localizationOptions;
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
...
app.UseRequestLocalization(GetLocalizationOptions());
...
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
I created an empty LocaleResources.razor control at the root of the project (this is a trick used to inject a single resource file to all components).
I included a routing controller to change language :
[Route("[controller]/[action]")]
public class CultureController : Controller {
public IActionResult SetCulture(string culture, string redirectUri) {
if (culture != null) {
HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture)));
}
return LocalRedirect(redirectUri);
}
}
And the language UI switcher looks like this (I use SyncFusion control here, but it could be any lookup actually, that shouldn't really matter)
#inject NavigationManager NavigationManager
#inject IConfiguration Configuration
<SfComboBox TValue="string" TItem="Tuple<string, string>" Placeholder="Select language" DataSource="#Cultures"
#bind-Value="selectedCulture" CssClass="lan-switch" Width="80%">
<ComboBoxFieldSettings Text="Item2" Value="Item1"></ComboBoxFieldSettings>
</SfComboBox>
<style>
.lan-switch {
margin-left: 5%;
}
</style>
#code {
string _activeCulture = System.Threading.Thread.CurrentThread.CurrentCulture.Name;
private string selectedCulture {
get => _activeCulture;
set {
_activeCulture = value;
SelectionChanged(value);
}
}
List<Tuple<string, string>> Cultures;
protected override void OnInitialized() {
var cultures = Configuration.GetSection("Cultures")
.GetChildren().ToDictionary(x => x.Key, x => x.Value);
Cultures = cultures.Select(p => Tuple.Create<string, string>(p.Key, p.Value)).ToList();
}
protected override void OnAfterRender(bool firstRender) {
if (firstRender && selectedCulture != AgendaSettings.SelectedLanguage) {
selectedCulture = AgendaSettings.SelectedLanguage;
}
}
private void SelectionChanged(string culture) {
if (string.IsNullOrWhiteSpace(culture)) {
return;
}
AgendaSettings.SelectedLanguage = culture;
var uri = new Uri(NavigationManager.Uri)
.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var query = $"?culture={Uri.EscapeDataString(culture)}&" +
$"redirectUri={Uri.EscapeDataString(uri)}";
NavigationManager.NavigateTo("/Culture/SetCulture" + query, forceLoad: true);
}
}
Finally, to the injection. I inject the IStringLocalizer to pages as follows and it works perfectly fine on razor controls:
#inject IStringLocalizer<LocaleResources> _loc
<h2>#_loc["hello world"]</h2>
Above, when I change language, the page displays the value in the corresponding resource file.
Now, to services: the MySingletonService and MyScopedService are registered at startup. They both have a constructor like
protected IStringLocalizer<LocaleResources> _loc;
public MySingletonService(IStringLocalizer<LocaleResources> loc) {
_loc = loc;
}
public void someMethod() {
Console.WriteLine(_loc["hello world"])
}
I run someMethod on a timer. Strangely, when I break on the above line, the result seems to oscillate : once it returns the default language's value, once the localized one...!
The answer to my question was: your code is correct!
The reason, I found out, is that I use a Scoped service that is started on the default App's start page:
protected async override Task OnAfterRenderAsync(bool firstRender) {
if (firstRender) {
MyScopedService.StartTimer();
}
await base.OnAfterRenderAsync(firstRender);
}
When users change language, the whole page is refreshed and a new instance of the scoped service is created and timer started. As my service did not implement IDisposable, the timer was not actually stopped.
So 2 solutions here:
use singleton services
make servcie disposable and ensure tasks are cancelled when service is disposed of.
Into class I get logged user like
public static GetUserById_Result GetUser(string userId)
{
GetUserById_Result user = new GetUserById_Result();
try
{
using (EF.SSMA oContext = new EF.SSMA())
{
user = oContext.GetUserById(userId).FirstOrDefault();
}
}
catch (Exception)
{
throw;
}
return user;
}
So it runs fine. But in the same class I want to acces user value into another method
public static List<GetUsers_Result> SelectAll()
{
List<GetUsers_Result> lstResult = new List<GetUsers_Result>();
try
{
using (EF.SSMA oContext = new EF.SSMA())
{
lstResult = oContext.GetUsers().Where().ToList();
}
}
catch (Exception ex)
{
throw ex;
}
return lstResult;
}
What I need to do to achieve that?, into controller is really simple I just do this:
var users = User.Identity.GetUserId();
GetUserById_Result currentUser = UserClass.GetUser(users);
var role = currentUser.BranchOfficeId;
But how can I acces it in the same class' I try to call GetUserId with
System.Web.HttpContext.Current.User.Identity.GetUserId();
but it just mark HttpContext in red and say "Cannot resolve symbol 'HttpContext'"
My target is to call only users who BranchOfficeId = user.BranchOfficeId
Help is very appreciated. Regards
If i understand your question well, make sure that you already installed the package Microsoft.Owin.Host.SystemWeb and add using System.Web, then use directely User.Identity.GetUserId()
The reason you can do this in the controller is because Controller has an HttpContext property, which has been created with the details of the current request, including the identity of the current user.
If you want to access the user from another class, you need to pass the user as an argument to your method. As an example:
using System.Security.Principal;
public class SomeClass
{
public void SomeMethod(IPrincipal user)
{
// Whatever you need
}
}
Then in your controller:
var someClass = new SomeClass();
someClass.SomeMethod(HttpContext.User);
However, if you're only interested in the user's name, then you can actually just pass a string instead:
public class SomeClass
{
public void SomeMethod(string username)
{
// Whatever you need
}
}
Then in your controller:
var someClass = new SomeClass();
someClass.SomeMethod(HttpContext.User.Identity.Name);
I am trying to POST an object from a WebJob to an MVC 4 controller. I am using Entity Framework. In the controller, I cannot get the object to bind properly (the argument is null). I have looked at many tutorials and it seems like my code should work.
Model (does this need to be in a specific namespace for EF to find it?):
public class CreateListingObject
{
public Listing listing;
public List<GalleryImage> images;
public CreateListingObject()
{
listing = new Listing();
images = new List<GalleryImage>();
}
}
public struct GalleryImage
{
public string picURL;
public string caption;
}
POST:
public void PostListing(CreateListingObject o)
{
Console.WriteLine("Posting listing: {0}", o.listing.Title);
HttpClient _httpClient = new HttpClient();
Uri uri = new Uri(_serviceUri, "/Automaton/CreateTestListing");
string json = BizbotHelper.SerializeJson(o);
HttpResponseMessage response = BizbotHelper.SendRequest(_httpClient, HttpMethod.Post, uri, json);
string r = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
}
SendRequest (thank you Azure search samples):
public static HttpResponseMessage SendRequest(HttpClient client, HttpMethod method, Uri uri, string json = null)
{
UriBuilder builder = new UriBuilder(uri);
//string separator = string.IsNullOrWhiteSpace(builder.Query) ? string.Empty : "&";
//builder.Query = builder.Query.TrimStart('?') + separator + ApiVersionString;
var request = new HttpRequestMessage(method, builder.Uri);
if (json != null)
{
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
}
return client.SendAsync(request).Result;
}
Controller Action fragment (o is an empty object here):
[HttpPost]
public ActionResult CreateTestListing(CreateListingObject o)
{
Listing li = o.listing;
I have confirmed that if I post a simple object using the same code, everything works as expected.
Instead of sending a CreateListingObject in PostListing, I send this instead:
var test = new
{
data = "hi mom"
};
And change my action to, then the argument gets bound and I get valid data:
[HttpPost]
public ActionResult CreateTestListing(string data)
{
I have also checked the serialization of my CreateListingObject in the WebJob, and it is fully populated as I expect. This leads me to suspect that I am falling afoul of the default ModelBinder.
Is there a way how to perform a "database" check through the Client Side Validation in MVC?
I have the following class
public class EmailCheck : ValidationAttribute,IClientValidatable
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string sErrorMessage = "Email already exists";
return new ValidationResult(sErrorMessage);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
ModelClientValidationRule validationRule = new ModelClientValidationRule();
validationRule.ValidationType = "checkemail";
validationRule.ErrorMessage = "Invalid Email Format";
validationRule.ValidationParameters.Add("param", "");
return new List<ModelClientValidationRule> { validationRule };
}
}
I would like the Client Side Validation to call the "IsValid" method onkeyup/lost focus as well and not just do the regular javascript checks in the "checkemail" javascript function.
The javascript function i have is the following:
//Validation for Well-Formed Email
jQuery.validator.addMethod("checkemail",
function (value, element, param) {
var emailReg = /^([\w-\.]+#([\w-]+\.)+[\w-]{2,4})?$/;
if (!emailReg.test(value))
return false;
return true;
});
jQuery.validator.unobtrusive.adapters.add("checkemail", ["param"], function (options) {
options.rules["checkemail"] = options.params.param;
options.messages["checkemail"] = options.message;
});
I would appreciate if anyone could guide me in the right direction or provide a tutorial of something similar.
Thanks
In your model try to use the "Remote" attribute
http://msdn.microsoft.com/en-us/library/gg508808%28v=vs.98%29.aspx
Rusty is correct, you need to use the Remote attribute in your model
Here is another example with complete detail only for email validation in MVC
MVC model validation for email from database in asp.net
Given the following scenario, where should I put logic to bind Department to Review:
Entities:
Dealership (has many departments)
Department (has one type)
DepartmentType
Review (has one dealership and one department)
On my ReviewForm I need the user to be able to select a Dealership and a DepartmentType, and then in some form of callback or pre/post bind, work out from them which Department to bind to the Review.
I also need this to happen prior to validation so that I can validate that the Department is child of the Dealership.
Note: Review relates to both Dealership and Department when it could just relate to Department to ease traversal and other logic I have going on.
There are two approaches I've tried so far but reached deadends / confusion.
DataTransformer on the DepartmentType on the form, not sure I understand this properly, my transform / reverseTransform methods were getting passed in the Review object, not the field object.
PRE_BIND, happens before validation but I only have raw data to work with, no objects
POST_BIND, happens after validation :(
For the final step of validation of the relationship I have a relatively simple validator that should do the job, but I'm not sure at what point I am meant to bind data to the object like this. Any pointers?
As validation is also done in a POST_BIND listener, you could simply add your POST_BIND listener with a higher priority than the validation listener (i.e. anything > 0).
If you're writing a listener:
$builder->addEventListener(FormEvents::POST_BIND, $myListener, 10);
and if you're writing a subscriber:
public static function getSubscribedEvents()
{
return array(
FormEvents::POST_BIND => array('postBind', 10),
);
}
public function postBind(FormEvent $event)
{
...
}
I would go with a standard (ie: non-Doctrine) choice type containing a choice to represent each DepartmentType.
Then use a DataTransformer to turn the selected option into the relevant type, and vice versa.
Your custom FormType should end up looking something like this:
class Department extends AbstractType
{
private $em;
public function __construct(EntityManager $em) {
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new DepartmentToTypeTransformer($this->em);
$builder->addViewTransformer($transformer, true);
$builder->getParent()->addEventListener(FormEvents::PRE_BIND, function($event) use ($transformer) {
$data = (object) $event->getData();
$transformer->setDealership($data->dealership);
});
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'department';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$choices = array();
foreach ($this->em->getRepository('AcmeDemoBundle:DepartmentType')->findAll() as $type) {
$choices[$type->getId()] = (string) $type;
}
$resolver->setDefaults(array(
'choices' => $choices,
'expanded' => true
));
}
}
Note the passing of the Dealership into the DataTransformer for use in the transformation.
And DataTransformer something like this:
class DepartmentToTypeTransformer implements DataTransformerInterface
{
private $em;
private $dealership;
public function __construct($em)
{
$this->em = $em;
}
public function transform($department)
{
if (null === $department) {
return $department;
}
return $department->getType()->getId();
}
public function reverseTransform($type)
{
if (null === $type) {
return $type;
}
return $this->em->getRepository('AcmeDemoBundle:Department')->findOneBy(array(
'dealership' => $this->getDealership(),
'type' => $type
));
}
public function getDealership() {
return $this->dealership;
}
public function setDealership($dealership) {
$this->dealership = $dealership;
return $this;
}
}
Your confusion regarding what is being passed to your transformer is most likely caused by the transformer you're binding being appended to pre-existing behaviour, try adding true as the second parameter to addViewTransformer:
$transformer = new DepartmentToTypeTransformer($this->em);
$builder->addViewTransformer($transformer, true);
From the docs:
FormBuilder::addViewTransformer(
DataTransformerInterface $viewTransformer,
Boolean $forcePrepend = false
)
Appends / prepends a transformer to the view transformer chain.