How to get Geo Location Blazor Server - geolocation

I am currently working on a blazor server project based on .NET 6. there is a requirement to get the geo location into the application. I have tried several ways but they don't work.is there anyone who knows about it ?? please help me.

This shows how to call the browser API to get the current position.
geoLocationJsInterop.js (wwwroot\scripts)
export function getCurrentPosition(dotNetHelper) {
const options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};
function success(pos) {
const coord = {
latitude: pos.coords.latitude,
longitude: pos.coords.longitude,
accuracy: pos.coords.accuracy
};
dotNetHelper.invokeMethodAsync('OnSuccessAsync', coord);
}
function error(err) {
console.warn(`ERROR(${err.code}): ${err.message}`);
}
navigator.geolocation.getCurrentPosition(success, error, options);
}
GeoLocation.razor
#using Microsoft.JSInterop
#implements IAsyncDisposable
<button #onclick=#GetLocationAsync>Get Location</button>
#if (geoCoordinates is not null)
{
<div>
Latitude : #geoCoordinates.Latitude <br />
Longitude : #geoCoordinates.Longitude<br />
Accuracy : #geoCoordinates.Accuracy
</div>
}
#code {
private readonly Lazy<Task<IJSObjectReference>> moduleTask = default!;
private readonly DotNetObjectReference<GeoLocation> dotNetObjectReference;
private GeoCoordinates? geoCoordinates = null;
[Inject]
private IJSRuntime jsRuntime { get; set; } = default!;
public GeoLocation()
{
moduleTask = new(() => jsRuntime!.InvokeAsync<IJSObjectReference>(
identifier: "import",
args: "./_content/ChatClient.Core/scripts/geoLocationJsInterop.js")
.AsTask());
dotNetObjectReference = DotNetObjectReference.Create(this);
}
public async Task GetLocationAsync()
{
var module = await moduleTask.Value;
await module.InvokeVoidAsync(identifier: "getCurrentPosition", dotNetObjectReference);
}
[JSInvokable]
public async Task OnSuccessAsync(GeoCoordinates geoCoordinates)
{
this.geoCoordinates = geoCoordinates;
await InvokeAsync(StateHasChanged);
}
public async ValueTask DisposeAsync()
{
if (moduleTask.IsValueCreated)
{
var module = await moduleTask.Value;
await module.DisposeAsync();
}
}
public class GeoCoordinates
{
public double Latitude { get; set; }
public double Longitude { get; set; }
public double Accuracy { get; set; }
}
}
Note: Change the path of the .js file to suite your project. In this case I was referencing a RCL called ChatClient.Core.
args: "./scripts/geoLocationJsInterop.js" for your root project.
This component runs on either wasm or blazor-server.
API Docs

Related

Custom Validation for nested model in .net core

I am trying to validate a nested model using custom validation. But the problem is AttributeAdapterBase.AddValidation function is never called on nested model. However it works well with simple class property
Custom required validation attribute:
public interface IId
{
long Id { get; set; }
}
public class Select2RequiredAttribute : RequiredAttribute
{
public Select2RequiredAttribute(string errorMessage = "") : base()
{
ErrorMessage = errorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Type t = value.GetType();
if (typeof(IId).IsAssignableFrom(t))
{
if ((value as IId).Id == 0)
{
return new ValidationResult(ErrorMessage);
}
}
else
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Attribute adapter base:
public class Select2RequiredAttributeAdapter : AttributeAdapterBase<Select2RequiredAttribute>
{
public Select2RequiredAttributeAdapter(Select2RequiredAttribute attribute, IStringLocalizer stringLocalizer) : base(attribute, stringLocalizer)
{
}
public override void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-select2-required", GetErrorMessage(context));
}
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
return Attribute.ErrorMessage ?? GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
}
}
Adapter provider:
public class Select2RequiredAdapterProvider : IValidationAttributeAdapterProvider
{
private readonly IValidationAttributeAdapterProvider _baseProvider = new ValidationAttributeAdapterProvider();
public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
{
if (attribute is Select2RequiredAttribute)
{
return new Select2RequiredAttributeAdapter(attribute as Select2RequiredAttribute, stringLocalizer);
}
else
{
return _baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
}
}
}
Startup.cs:
services.AddSingleton<IValidationAttributeAdapterProvider, Select2RequiredAdapterProvider>();
Model classes:
public interface IBaseBriefViewModel : IId
{
string Name { get; set; }
}
public class BaseBriefViewModel : IBaseBriefViewModel
{
public virtual long Id { get; set; }
public string Name { get; set; }
}
public class UserViewModel
{
public long Id { get; set; }
public string Name { get; set; }
[Select2Required("Branch is required.")]
public BaseBriefViewModel Branch { get; set; }
}
Branch select 2 partial view:
#model DataLibrary.ViewModels.BriefViewModels.BaseBriefViewModel
#{
var elementId = ViewData["ElementId"] != null && !string.IsNullOrEmpty(ViewData["ElementId"].ToString()) ? ViewData["ElementId"].ToString() : "branch-id";
}
<div class="form-group">
<label>Branch: <span class="text-danger"></span></label>
<div class="row">
<div class="#select2Class">
#Html.DropDownListFor(model => model.Id, new List<SelectListItem>() {
new SelectListItem()
{
Value = (Model!=null&&Model.Id>0)?Model.Id.ToString():"",
Text = (Model!=null&&Model.Id>0)?Model.Name:"",
Selected = (Model!=null&&Model.Id>0)?true:false,
}}, new { #id = elementId, #class = "form-control disable-field"})
#Html.ValidationMessageFor(model => model.Id, "", new { #class = "text-danger" })
</div>
</div>
</div>
<script>
$(function () {
var id = "#" + "#elementId";
var url = '/Branch/GetBranchsForSelect2';
var dataArray = function (params) {
params.page = params.page || 1;
return {
prefix: params.term,
pageSize: pageSize,
pageNumber: params.page,
};
};
Select2AutoCompleteAjax(id, url, dataArray, pageSize, "---Branch---");
});
</script>
All this code works well for server side. But for better user experience I want to show error before submitting form. How can I achieve this? I want to use this BaseBriefViewModel for a lot of Select2 in the project. So hard coding a static error message is not a good idea. What I really want to do is pass a error message from parent object. Like Branch is required in this specific case. Maybe in some other class I might pass Product is required
Any direction will be appreciated
At the moment this is not supported - but support is in planned. See dotnet github issue:
https://github.com/dotnet/runtime/issues/36093

MvvmCross object data binding

I am new to MvvmCross, I have a question with regards to binding in Android. I can bind to single property but unable to data bind to an object. Not sure what I am doing wrong but here it is:
Model class:
public class Login : MvxNotifyPropertyChanged
{
private string _email;
public string Email
{
get { return _email; }
set
{
SetProperty(ref _email, value);
}
}
public string Password { get; set; }
}
Snippet of View Model Class:
public class LoginOptionViewModel: MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public LoginOptionViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
LoginCommand =
new MvxAsyncCommand(async () => await _navigationService.Navigate<RegistrationViewModel>());
}
public IMvxAsyncCommand LoginCommand { get; set; }
private Login _loginInfo;
public Login LoginInfo
{
get
{
return _loginInfo ?? new Login();
}
set
{
_loginInfo = value;
RaisePropertyChanged(() => LoginInfo);
}
}
}
Snippet of Android Axml:
<EditText
android:id="#+id/loginEmailTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/EmailHint"
android:textColor="#color/darkNavy"
android:inputType="textEmailAddress"
local:MvxBind="Text LoginInfo.Email" />
Where am I going wrong, I have placed a breakpoint but do not see it being hit. This is binded to EditText but nothing appears to happen. Am I missing or doing something wrong in order to bind to an object property ?
MvvmCross object data binding
You could implement the MvxNotifyPropertyChanged interface, so the system could notifies clients that a property value has changed.
Modify your object to :
public class Login : MvxNotifyPropertyChanged
{
private string _email;
public string Email
{
get => _email;
set => SetProperty(ref _email, value);
}
}
In the MainViewModel :
private Login _login;
public Login Login
{
get
{
return _login ?? new Login() { Email = "=-="};
}
set
{
_login = value;
RaisePropertyChanged(() => Login);
}
}
Then use it in axml :
local:MvxBind="Text Login.Email"
It works fine on my side.
Update :
I cant reproduce your problem, but here is my complete code, hope this can help you :
public class MainViewModel : MvxViewModel
{
public MainViewModel()
{
}
public override Task Initialize()
{
return base.Initialize();
}
public IMvxCommand ResetTextCommand => new MvxCommand(ResetText);
private void ResetText()
{
Text = "Hello MvvmCross";
}
private string _text = "Hello MvvmCross";
public string Text
{
get { return _text; }
set { SetProperty(ref _text, value); }
}
private Login _login;
public Login Login
{
get
{
return _login ?? new Login() { Email = "=-="};
}
set
{
_login = value;
RaisePropertyChanged(() => Login);
}
}
}
public class Login : MvxNotifyPropertyChanged
{
private string _email;
public string Email
{
get => _email;
set => SetProperty(ref _email, value);
}
}
Effect.

MvvmCross ILocationWatcher Success Action not firing - Android

I'm trying to integrate the MvxGeolocation plugin for android and I'm having issues getting the watcher to fire the success event. It just does not fire.
I've followed the documentation from MvvmCross (https://github.com/MvvmCross/MvvmCross/wiki/MvvmCross-plugins#location).
As per the documentation I've created a separate Service that publishes messages to the ViewModel, rather than have the IMvxLocationWatcher hooked directly up to the ViewModel.
public class GeoLocationService: IGeoLocationService
{
private readonly IMvxLocationWatcher _watcher;
private readonly IMvxMessenger _messenger;
public GeoLocationService(IMvxLocationWatcher watcher, IMvxMessenger messenger)
{
_watcher = watcher;
_messenger = messenger;
_watcher.Start(new MvxLocationOptions(), OnLocation, OnError);
}
public void OnLocation(MvxGeoLocation location)
{
var message = new LocationMessage(this, location.Coordinates.Latitude, location.Coordinates.Longitude);
_messenger.Publish(message);
}
public void OnError(MvxLocationError error)
{
Mvx.Error("Seen location error {0}", error.Code);
}
}
The OnLocation(MvxGeoLocation location) method is meant to publish a message that my ViewModel subscribes too as you can see below:-
public class MainViewModel : MvxViewModel
{
private readonly MvxSubscriptionToken _token;
public MainViewModel(IMvxMessenger messenger)
{
_token = messenger.SubscribeOnMainThread<LocationMessage>(OnLocationMessage);
}
private void OnLocationMessage(LocationMessage locationMessage)
{
Lat = locationMessage.Lat;
Lng = locationMessage.Lng;
}
double _latitude;
public double Lat
{
get
{
return _latitude;
}
set
{
_latitude = value;
RaisePropertyChanged(() => Lat);
}
}
double _longitude;
public double Lng
{
get
{
return _longitude;
}
set
{
_longitude = value;
RaisePropertyChanged(() => Lng);
}
}
string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
RaisePropertyChanged(() => Name);
}
}
}
I've added Bootstrapper classes to the android application that resolve both the Messenger and LocationWatcher plugins. Ive also registered the GeoLocationService in the App class in my core library like this :-
public class App : MvxApplication
{
public App()
{
Mvx.ConstructAndRegisterSingleton<IGeoLocationService, GeoLocationService>();
Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<MainViewModel>());
}
}
I've also edited the AndroidManifest.Xml and added the following user permissions :-
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Im debugging this against a plugged in Device (Samsung S6 Edge).
Ive tested my view.xaml to ensure that binding is working by passing it some default values.. and everything is as expected.
The issue is that The Action in the Watcher class for Success or Error are never called.

Castle.Windsor - How to implement TypedFactoryFacility

Recently, I developed a component , using factory pattern. However, I did a research. on how to improve it using TypedFactoryFacility, since we are using Castle.WIndsor.
Can you please provide a simple complete example? I have read few of them but still can't really fully understand . SO far, my code looks like that :
public class DynamoStoreService : IDynamoStoreService
{
private IDynamoStoreFactory _dynamoStoreFactory;
public DynamoStoreService(IDynamoStoreFactory dynamoStoreFactory)
{
_dynamoStoreFactory=dynamoStoreFactory;
}
public IDynamoStore GetProductDataDynamoStore(string storageAccount)
{
return _dynamoStoreFactory.Create(storageAccount);
}
}
public class DynamoStoreFactory : IDynamoStoreFactory
{
private IStorageAccountSelector _storageAccountSelector;
public DynamoStoreFactory(IStorageAccountSelector storageAccountSelector)
{
_storageAccountSelector = storageAccountSelector;
}
public IDynamoStore Create(string storageAccount)
{
return new AzureKeyValueStore(_storageAccountSelector.GetCredentials(storageAccount).StorageAccount, "pointerfiles");
}
}
public class StorageAccountSelector : IStorageAccountSelector
{
private readonly IConfigurationSettings _settings;
public StorageAccountSelector(IConfigurationSettings settings)
{
_settings = settings;
}
BlobCredentials IStorageAccountSelector.GetCredentials(string storageAccount)
{
return new BlobCredentials()
{
Container = string.Empty,
StorageAccount = GetStorageAccount(storageAccount)
};
}
private string GetStorageAccount(string storageAccount)
{
switch (storageAccount)
{
case "CustomerPolarisingCategoryBlobStorageAccountKey":
return _settings.CustomerPolarisingCategoryBlobStorageAccount;
case "CustomerPolarisingSegmentBlobStorageAccountKey":
return _settings.CustomerPolarisingSegmentBlobStorageAccount;
case "P2ProductSimilarityBlobStorageAccountKey":
return _settings.P2ProductSimilarityBlobStorageAccount;
case "ProductPolarisingCategoryBlobStorageAccountKey":
return _settings.ProductPolarisingCategoryBlobStorageAccount;
case "ProductPolarisingSegmentBlobStorageAccountKey":
return _settings.ProductPolarisingSegmentBlobStorageAccount;
case "SignalBlobStorageAccountKey":
return _settings.SignalBlobStorageAccount;
}
return string.Empty;
}
}
}
So basically, the IDynamostore , whenvever called, we need to be able to pass a different connection string. I have figured out the above design.. could this be improved using TypedFactoryFacility?
Thanks
Maybe the code below can give you an idea about how to use the TypedFactoryFacility. If you have studied it and have questions about it, please let me know.
Kind regards,
Marwijn.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Castle.Facilities.TypedFactory;
using Castle.MicroKernel;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
namespace ConsoleApplication3
{
public class TypedFactoryComponentSelector : DefaultTypedFactoryComponentSelector
{
private readonly StorageAccountSelector _storageAccountSelector;
public TypedFactoryComponentSelector(StorageAccountSelector storageAccountSelector)
{
_storageAccountSelector = storageAccountSelector;
}
protected override System.Collections.IDictionary GetArguments(MethodInfo method, object[] arguments)
{
var dictionary = new Dictionary<string, object>();
dictionary.Add("mappedStorageAccount", _storageAccountSelector.GetCredentials((string)arguments[0]).StorageAccount);
dictionary.Add("files", "pointerfiles");
return dictionary;
}
}
public interface IDynamoStore
{
}
public class AzureKeyValueStore : IDynamoStore
{
public AzureKeyValueStore(string mappedStorageAccount, string files)
{
Console.WriteLine(mappedStorageAccount);
Console.WriteLine(files);
}
}
public class BlobCredentials
{
public string Container { get; set; }
public string StorageAccount { get; set; }
}
public interface IDynamoStoreFactory
{
IDynamoStore Create(string storageAccount);
}
public class StorageAccountSelector
{
public BlobCredentials GetCredentials(string storageAccount)
{
return new BlobCredentials()
{
Container = string.Empty,
StorageAccount = GetStorageAccount(storageAccount)
};
}
public string GetStorageAccount(string storageAccount)
{
return storageAccount + "Mapped";
return string.Empty;
}
}
class Program
{
static void Main(string[] args)
{
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<IDynamoStoreFactory>().AsFactory(new TypedFactoryComponentSelector(new StorageAccountSelector())),
Component.For<IDynamoStore>().ImplementedBy<AzureKeyValueStore>()
);
var factory = container.Resolve<IDynamoStoreFactory>();
factory.Create("storageAccount");
}
}
}

uCommerce - add dynamic property to order line

I have hit a problem building a uCommerce site based on top of the demo razor store available http://thesitedoctor.co.uk/portfolio/avenue-clothingcom/
The demo uses servicestack and the ucommerceapi for its basket functions.
I am trying to add a dynamic property to the basket (on an order line) at the point where the user clicks buy. I traced through the productpage.js file and amended the code to add a new property ('message'):
function (data) {
var variant = data.Variant;
$.uCommerce.addToBasket(
{
sku: variant.Sku,
variantSku: variant.VariantSku,
quantity: qty,
message: $('#personalisedMessage').val()
},
function () {
updateCartTotals(addToCartButton);
}
);
});
using firebug, i checked the data that is being posted
addToExistingLine: true
message: "this is a message"
quantity:"1"
sku: "Product (options: none)"
variantSku:""
Posting this does not cause an error, but I cannot tell if it has worked either - I cannot find it in the database, assuming that it would be stored in OrderProperty table. In this scenario, I am 'buying' a product with no variations.
Any help is greatly appreciated with this.
Out of the box you can't add order/line item properties via the API like that. The API payload that you've added to is specified although valid JSON won't get interpreted/used by the API.
Instead what you'll need to do is add your own method to the API. To do this you'll need to implement a service from IUCommerceApiService and then you can do what you need. I've created an example (untested) below and will get it added to the demo store as I think it's a useful bit of functionality to have.
public class AddOrderLineProperty
{
public int? OrderLineId { get; set; }
public string Sku { get; set; }
public string VariantSku { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
public class AddOrderLinePropertyResponse : IHasResponseStatus
{
public AddOrderLinePropertyResponse() { }
public AddOrderLinePropertyResponse(UCommerce.EntitiesV2.OrderLine line)
{
if (line == null)
{
UpdatedLine = new LineItem();
return;
}
var currency = SiteContext.Current.CatalogContext.CurrentCatalog.PriceGroup.Currency;
var lineTotal = new Money(line.Total.Value, currency);
UpdatedLine = new LineItem()
{
OrderLineId = line.OrderLineId,
Quantity = line.Quantity,
Sku = line.Sku,
VariantSku = line.VariantSku,
Price = line.Price,
ProductName = line.ProductName,
Total = line.Total,
FormattedTotal = lineTotal.ToString(),
UnitDiscount = line.UnitDiscount,
VAT = line.VAT,
VATRate = line.VATRate
};
}
public ResponseStatus ResponseStatus { get; set; }
public LineItem UpdatedLine { get; set; }
}
public class AddOrderLinePropertyService : ServiceBase<AddOrderLineProperty>, IUCommerceApiService
{
protected override object Run(AddOrderLineProperty request)
{
var orderLineId = request.OrderLineId;
var sku = request.Sku;
var variantSku = request.VariantSku;
var orderLine = findOrderLine(orderLineId, sku, variantSku);
addPropertyToOrderLine(orderLine, request.Key, request.Value);
TransactionLibrary.ExecuteBasketPipeline();
var newLine = findOrderLine(orderLineId, sku, variantSku);
return new AddOrderLinePropertyResponse(newLine);
}
private void addPropertyToOrderLine(OrderLine orderLine, string key, string value)
{
if (orderLine == null)
return;
orderLine[key] = value;
orderLine.Save();
}
private static OrderLine findOrderLine(int? orderLineId, string sku, string variantSku)
{
return orderLineId.HasValue
? getOrderLineByOrderLineId(orderLineId)
: getOrderLineBySku(sku, variantSku);
}
private static OrderLine getOrderLineBySku(string sku, string variantSku)
{
return String.IsNullOrWhiteSpace(variantSku)
? getOrderLines().FirstOrDefault(l => (l.Sku == sku))
: getOrderLines().FirstOrDefault(l => (l.Sku == sku && l.VariantSku == variantSku));
}
private static OrderLine getOrderLineByOrderLineId(int? orderLineId)
{
return getOrderLines().FirstOrDefault(l => l.OrderLineId == orderLineId);
}
private static ICollection<OrderLine> getOrderLines()
{
return TransactionLibrary.GetBasket().PurchaseOrder.OrderLines;
}
}
You'll need to add the new method to uCommerce.jQuery.js as well something like this:
addOrderLineProperty: function (options, onSuccess, onError) {
var defaults = {
orderLineId: 0
};
var extendedOptions = $.extend(defaults, options);
callServiceStack({ AddOrderLineProperty: extendedOptions }, onSuccess, onError);
}
Let me know if you have any issues using it.
Tim

Resources