Repeating ViewComponent data - c#

I have a customer view model which has drop down lists for selecting country, area and language. I am using ViewComponent to load the necessary data for the dropdowns and it's working like a charm. My problem is that when they are multiple client models on the page I am making multiple calls to the external API to receive the same data. I tried to put the Component.InvokeAsync part inside a cache tag helper but it also keeps the elements naming from the first call and messes up the model binding. Is there a way to avoid the multiple calls for same data?
Here is what the code looks like. It's nothing special. The views just bind the properties and there isn't anything special there.
[ViewComponent(Name = "LocationSelector")]
public class LocationSelector : ViewComponent
{
private readonly ILocationService locationsService;
public LocationSelector(ILocationService locationService)
{
this.locationsService = locationService;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var locationManager = new LocationSelectorViewModel();
locationManager.Areas = await this.locationsService.GetAreas();
locationManager.Countries = await this.locationsService.GetCountries();
return View("Default", locationManager);
}
}
And the component is called inside the customer model like this. The problem is that I may have multiple customers in my page and they all will make requests to the service for the exact same data.
#await Component.InvokeAsync(nameof(LocationSelector))

You should consider caching the data. You can use the IMemoryCache imeplemenation. I prefer to create my own abstraction layer which i would use it wherever i need.
public interface ICache
{
T Get<T>(string key, Func<T> updateExpression = null, int cacheDuration=0);
}
public class InMemoryCache : ICache
{
readonly IMemoryCache memoryCache;
public InMemoryCache(IMemoryCache memoryCache)
{
this.memoryCache = memoryCache;
}
public T Get<T>(string key, Func<T> updateExpression = null,int cacheDuration=0)
{
object result;
if (memoryCache.TryGetValue(key,out result))
{
return (T) result;
}
else
{
if (updateExpression == null)
{
return default(T);
}
var data = updateExpression();
if (data != null)
{
var options = new MemoryCacheEntryOptions
{
AbsoluteExpiration = DateTime.Now.AddSeconds(cacheDuration)
};
this.memoryCache.Set(key, data, options);
return data;
}
return default(T);
}
}
}
Now in your startup class's ConfigureServices method, enable caching and define our custom ICache-InMemoryCache mapping.
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ICache, InMemoryCache>();
services.AddMemoryCache();
}
Now you can inject ICache to any class and use it to get/store data to it.
public class LocationSelector : ViewComponent
{
private readonly ILocationService locationsService;
private readonly ICache cache;
public LocationSelector(ILocationService locationService,ICache cache)
{
this.locationsService = locationService;
this.cache = cache;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var locationManager = new LocationSelectorViewModel();
var areas = await this.cache.Get(CacheKey.Statuses, () =>
this.locationsService.GetAreas(),360);
locationManager.Areas = areas;
return View("Default", locationManager);
}
}

Related

Application_Start event on ASP.NET Core 6.0

How would I go about setting global variables in ASP.NET Core 6.0(razor pages)?
I have some information in the database, for example, ServiceName, ContactEmail and so on, and want to save it to my static class.
I don't want to access the database every time I need to display the information.
In addition, there aren't Global.asax in ASP.NET Core .
In ASP.NET MVC 5 (based on .net framework), I could do it like
// global.asax
protected void Application_Start() {
var context = new DefaultConnection();
MyConfig.ServiceName = context.GlobalSettings.SingleOrDefault().ServiceName;
// MyConfig is my static class
}
But I don't know where I should do it in ASP.NET Core project.
How can I do that? Please help me.
So lazy-loading is probably a very good choice for you.
Step 1: Create a data service that provides your data.
public interface IStaticDbData // Think of a better name!
{
public Task<string> GetContactEmailAsync();
public Task<string> GetServiceNameAsync();
// Etc.
}
public class StaticDbData : IStaticDbData
{
// Since we want a singleton, we'll have to synchronize the data fetching.
private object _lock = new object();
private string _contactEmail;
private string _serviceName;
// Etc.
// Try to create a single function that loads all of the data in one round trip to the DB.
// This will run in its own thread, so the calling thread can be awaited.
private Task LoadAllDataAsync()
=> Task.Run(() =>
{
lock (_lock)
{
//Re-check after locking.
if (_contactEmail != null)
{
return;
}
// Database code here to extract your data.
// Save to the individual fields.
}
});
public async Task<string> GetContactEmailAsync()
{
// See if data is there.
if (_contactEmail != null)
{
return _contactEmail;
}
// Data was not there. Load data.
await LoadAllDataAsync();
return _contactEmail;
}
public async Task<string> GetServiceNameAsync()
{
if (_serviceName != null)
{
return _serviceName;
}
await LoadAllDataAsync();
return _serviceName;
}
}
Step 2: Now that you have your service interface and service implementation, register the m in the IoC container. In program.cs:
builder.Services.AddSingleton<IStaticDbData, StaticDbData>();
Step 3: Consume the service as you would any other service.
public class SomeOtherServiceOrControllerOrWhatever
{
private IStaticDbData StaticDbDataSvc { get; }
// Constructor-injected.
public SomeOtherServiceOrControllerOrWhatever(IStaticDbData staticDbDataSvc)
{
StaticDbDataSvc = staticDbDataSvc;
}
}
NOTE: Make sure that your consuming services are also registered and resolved using the IoC container.
This is sudo code
You can create a static class with static properties:
public static class MyConfig
{
public static string Setting1 {set; get;}
...
}
then write a method to fetch data from your database and fill MyConfig and in the Program.cs file just call that method:
app.MapControllers();
app.Run();
CallYourMethodHere(); <-----
another is you can do this:
first create a static class:
public static class MyConfig
{
private static Dictionary<string, string> MyConfigs {set; get;}
private static Dictionary<string, string> GetConfigFromDatabase(bool forceToFill)
{
if(MyConfigs == null || MyConfigs.Any() == false || forceToFill == true)
{
//Fetch Data From Database and Fill MyConfig
}
}
public static string GetConfig(string configName)
{
return GetConfigFromDatabase(false)[configName];
}
}
In solution 2 you have to consider some thread-safe and race condition concepts.

IDistributedCache in .net core 2.0 is not shared between calls

I'm developing .NET Core 2.0 Web API and trying to use IDistributedCache to keep some data between different calls.
For this task I created wrapper and registered it as a singleton in Startup.cs file in order to use dependency injection in my controllers.
This is how the wrapper looks (simplified):
public interface ICacheWrapper
{
Task Set<T>(string name, T obj);
Task<T> TryGet<T>(string name);
}
public class CacheWrapper : ICacheWrapper
{
private readonly IDistributedCache _cache;
private readonly TimeSpan _defaultOffset = new TimeSpan(1, 0, 0, 0);
public CacheWrapper(IDistributedCache cache)
{
_cache = cache;
}
public async Task Set<T>(string name, T obj)
{
await DoSet(name, obj);
}
public async Task<T> TryGet<T>(string name)
{
var data = await Get<T>(name);
}
private async Task DoSet<T>(string name, T obj)
{
var binFormatter = new BinaryConverter();
var array = binFormatter.Serialize(obj);
var options = new DistributedCacheEntryOptions { SlidingExpiration = _defaultOffset };
await _cache.SetAsync(name, array, options);
}
private async Task<T> Get<T>(string name)
{
var binFormatter = new BinaryConverter();
var data = await _cache.GetAsync(name);
if (data == null)
{
return default(T);
}
var obj = binFormatter.Deserialize<T>(data);
return obj;
}
}
I also added this line to my Startup.cs file: services.AddDistributedMemoryCache();
The problem is: between each API call IDistributedCache object seems to reset - it does not contain any data. What should I do to keep the cache between calls?
Edit: I can confirm that adding to cache works in scope of one call, after doing SetAsync cache contains one item.
Edit2 This is how I register my wrapper : services.AddSingleton<ICacheWrapper, CacheWrapper>();

Is using Dependency Resolver a bad practice?

I am going to make up an example here just to get my point across. Please consider the following class:
public class MovieController : Controller
{
private readonly IMovieService _movieService;
private readonly IUserService _userService;
public MovieController(IMovieService movieService, IUserService userService)
{
_movieService = movieService;
_userService = userService;
}
public ViewModel GetMovies()
{
return View("Movies", _movieService.GetMovies());
}
public ViewModel GetAuthors()
{
return View("Authors", _userService.GetAuthors());
}
}
With the example above, whenever the MovieController is created, it will create both services. Each service will require its services and repositories in the constructor. So, in reality, I may be creating some classes each time MovieController is called. For this reason, I want to implement Lazy loading as I believe it will improve performance. For this reason, please consider the next class:
public class MovieController : Controller
{
private readonly IMovieService _movieService;
private readonly IUserService _userService;
private MovieService
{
get
{
if (_movieService == null) _movieService = new MovieService();
return _movieService;
}
}
private UserService
{
get
{
if (_userService == null) _userService = new UserService();
return _userService;
}
}
public MovieController() { }
public ViewModel GetMovies()
{
return View("Movies", MovieService.GetMovies());
}
public ViewModel GetAuthors()
{
return View("Authors", UserService.GetAuthors());
}
}
The problem with the above example is that I lost DI. Now I understand the benefit of DI, and I very much want to keep it, and as such, I came up with the following example:
public class MovieController : Controller
{
private readonly IMovieService _movieService;
private readonly IUserService _userService;
private MovieService
{
get
{
if (_movieService == null) _movieService = DependencyResolver.Current.GetService(typeof(IMovieService));
return _movieService;
}
}
private UserService
{
get
{
if (_userService == null) _userService = DependencyResolver.Current.GetService(typeof(IUserService));
return _userService;
}
}
public MovieController() { }
public ViewModel GetMovies()
{
return View("Movies", MovieService.GetMovies());
}
public ViewModel GetAuthors()
{
return View("Authors", UserService.GetAuthors());
}
}
Is my third example in this question a bad practice? If so, why? Am I losing on performance by doing this or is there another reason why this would be considered bad practice?
Using the dependency resolver this way means that you are using the service locator anti-pattern.
Is there any reason why you think that creating MovieService and UserService will have performance issues? Are you doing something significant in the constructor of these classes? If so, then you probably shouldn't. In the constructor you should almost always just accept the dependencies and store them in private fields.
If for some reason, you still want lazy loading, then you can create something like this:
public class LazyMovieService : IMovieService
{
private readonly Lazy<IMovieService> lazyInstance;
public LazyMovieService(Func<IMovieService> instanceFactory)
{
lazyInstance = new Lazy<IMovieService>(instanceFactory);
}
public string[] GetMovies()
{
return lazyInstance.Value.GetMovies();
}
}
This class allows you to use the first code example in your code while still being able to lazily load your services.
In the Composition Root, you would simply do something like:
var controller =
new MovieController(
new LazyMovieService(() => new MyOriginalMovieService(...)),
...);

C# webapi async context issue

I got two console applications which calls my webapi the same time and I get back in the console application the follow response from my api:
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
So they call at the same time my webapi and then something inside the webapi cannot handle those 2 async calls so this error is returned.
I checked all my code on the webapi project and all methods are async and got await so I cannot see why I get this.
Here is the code of the webapi.
Controller:
public class FederationsController : ApiController
{
private readonly IFederationRepository _federationRepository;
public FederationsController(IFederationRepository federationRepository)
{
_federationRepository = federationRepository;
}
[HttpGet]
[Route("federations", Name = "GetFederations")]
public async Task<IHttpActionResult> GetFederations()
{
var federations = await _federationRepository.GetAllAsync();
return Ok(federations.ToModel());
}
}
Repository
public class FederationRepository : IFederationRepository, IDisposable
{
private Models.DataAccessLayer.CompetitionContext _db = new CompetitionContext();
#region IQueryable
private IQueryable<Models.Entities.Federation> FederationWithEntities()
{
return _db.Federations.Include(x => x.Clubs)
.Where(x => !x.DeletedAt.HasValue && x.Clubs.Any(y => !y.DeletedAt.HasValue));
}
#endregion IQueryable
public async Task<IEnumerable<Models.Entities.Federation>> GetAllAsync()
{
return await FederationWithEntities().ToListAsync();
}
}
Mapper
public static class FederationMapper
{
public static List<Federation> ToModel(this IEnumerable<Models.Entities.Federation> federations)
{
if (federations == null) return new List<Federation>();
return federations.Select(federation => federation.ToModel()).ToList();
}
public static Federation ToModel(this Models.Entities.Federation federation)
{
return new Federation()
{
Name = federation.Name,
FederationCode = federation.FederationCode,
CreatedAt = federation.CreatedAt,
UpdatedAt = federation.UpdatedAt
};
}
}
DbContext
public class CompetitionContext : DbContext
{
public CompetitionContext() : base("ContextName")
{
}
public DbSet<Federation> Federations { get; set; }
}
UnityConfig
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterType<IFederationRepository, FederationRepository>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
}
Thank you for all the advices/help.
In your repository you are creating a single CompetitionContext and reusing it. I'm assuming that IoC setup is registring the repository as some kind of single instance, so the same repository is getting used every time. If that's the case you should create a new CompetitionContext for each method call.
Also, probably should make sure it's closed with a using statement.
I'm also not clear from your code snippets why you are returning an IQueryable from that FederationWithEntities, method, do you have other things that are using it?
Anyway, I'd probably change that GetAllMethod to be something like this:
public async Task<IEnumerable<Models.Entities.Federation>> GetAllAsync()
{
using (Models.DataAccessLayer.CompetitionContext _db = new CompetitionContext())
{
return _db.Federations.Include(x => x.Clubs)
.Where(x => !x.DeletedAt.HasValue && x.Clubs.Any(y => !y.DeletedAt.HasValue))
.ToListAsync();
}
}

MVC 6 Custom Model Binder with Dependency Injection

Right now my ViewModel looks like this:
public class MyViewModel
{
private readonly IMyService myService;
public ClaimantSearchViewModel(IMyService myService)
{
this.myService = myService;
}
}
My Controller that consumes this ViewModel looks like this:
public class MyController : Controller
{
private readonly IMyService myService;
public HomeController(IMyService myService)
{
this.myService = myService;
}
public IActionResult Index()
{
var model = new MyViewModel(myService);
return View(model);
}
[HttpPost]
public async Task<IActionResult> Find()
{
var model = new MyViewModel(myService);
await TryUpdateModelAsync(model);
return View("Index", model);
}
}
What I need is my Controller to look like is this:
public class MyController : Controller
{
private readonly IServiceProvider servicePovider;
public MyController(IServiceProvider servicePovider)
{
this.servicePovider = servicePovider;
}
public IActionResult Index()
{
var model = servicePovider.GetService(typeof(MyViewModel));
return View(model);
}
[HttpPost]
public IActionResult Index(MyViewModel model)
{
return View(model);
}
}
Right now, calling the first Index method works fine (with
builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource(x => x.Name.Contains("ViewModel")));
in my Startup class) but doing the POST to Index(MyViewModel model) gives you a No parameterless constructor defined for this object exception. I realize that a custom model binder that can use my DI will be the most likely solution... but I'm not able to find any help on how to even get started here. Please help me with this, especially for Autofac in MVC 6.
We got the answer here: https://github.com/aspnet/Mvc/issues/4167
And the answer is to use: [FromServices]
My Model ends up looking like this:
public class MyViewModel
{
[FromServices]
public IMyService myService { get; set; }
public ClaimantSearchViewModel(IMyService myService)
{
this.myService = myService;
}
}
Although it's sad to make that property public, it's much less sad than having to use a custom model binder.
Also, supposedly you should be able to pass [FromServices] as part of the param in the Action method, it does resolve the class, but that breaks the model binding... ie none of my properties got mapped. It looks like this: (but again, THIS DOES NOT WORK so use the above example)
public class MyController : Controller
{
... same as in OP
[HttpPost]
public IActionResult Index([FromServices]MyViewModel model)
{
return View(model);
}
}
UPDATE 1
After working with the [FromServices] attribute we decided that property injection in all of our ViewModels was not the way we wanted to go, especially when thinking about long term maintenance with testing. SO we decided to remove the [FromServices] attributes and got our custom model binder working:
public class IoCModelBinder : IModelBinder
{
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{
var serviceProvider = bindingContext.OperationBindingContext.HttpContext.RequestServices;
var model = serviceProvider.GetService(bindingContext.ModelType);
bindingContext.Model = model;
var binder = new GenericModelBinder();
return binder.BindModelAsync(bindingContext);
}
}
It's registered like this in the Startup ConfigureServices method:
services.AddMvc().AddMvcOptions(options =>
{
options.ModelBinders.Clear();
options.ModelBinders.Add(new IoCModelBinder());
});
And that's it. (Not even sure that options.ModelBinders.Clear(); is needed.)
UPDATE 2
After going through various iterations of getting this to work (with help https://github.com/aspnet/Mvc/issues/4196), here is the final result:
public class IoCModelBinder : IModelBinder
{
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{ // For reference: https://github.com/aspnet/Mvc/issues/4196
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
if (bindingContext.Model == null && // This binder only constructs viewmodels, avoid infinite recursion.
(
(bindingContext.ModelType.Namespace.StartsWith("OUR.SOLUTION.Web.ViewModels") && bindingContext.ModelType.IsClass)
||
(bindingContext.ModelType.IsInterface)
)
)
{
var serviceProvider = bindingContext.OperationBindingContext.HttpContext.RequestServices;
var model = serviceProvider.GetRequiredService(bindingContext.ModelType);
// Call model binding recursively to set properties
bindingContext.Model = model;
var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(bindingContext);
bindingContext.ValidationState[model] = new ValidationStateEntry() { SuppressValidation = true };
return result;
}
return await ModelBindingResult.NoResultAsync;
}
}
You'd obviously want to replace OUR.SOLUTION... with whatever the namespace is for your ViewModels Our registration:
services.AddMvc().AddMvcOptions(options =>
{
options.ModelBinders.Insert(0, new IoCModelBinder());
});
UPDATE 3:
This is the latest iteration of the Model Binder and its Provider that works with ASP.NET Core 2.X:
public class IocModelBinder : ComplexTypeModelBinder
{
public IocModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory) : base(propertyBinders, loggerFactory)
{
}
protected override object CreateModel(ModelBindingContext bindingContext)
{
object model = bindingContext.HttpContext.RequestServices.GetService(bindingContext.ModelType) ?? base.CreateModel(bindingContext);
if (bindingContext.HttpContext.Request.Method == "GET")
bindingContext.ValidationState[model] = new ValidationStateEntry { SuppressValidation = true };
return model;
}
}
public class IocModelBinderProvider : IModelBinderProvider
{
private readonly ILoggerFactory loggerFactory;
public IocModelBinderProvider(ILoggerFactory loggerFactory)
{
this.loggerFactory = loggerFactory;
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.Metadata.IsComplexType || context.Metadata.IsCollectionType) return null;
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
foreach (ModelMetadata property in context.Metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
return new IocModelBinder(propertyBinders, loggerFactory);
}
}
Then in Startup:
services.AddMvc(options =>
{
// add IoC model binder.
IModelBinderProvider complexBinder = options.ModelBinderProviders.FirstOrDefault(x => x.GetType() == typeof(ComplexTypeModelBinderProvider));
int complexBinderIndex = options.ModelBinderProviders.IndexOf(complexBinder);
options.ModelBinderProviders.RemoveAt(complexBinderIndex);
options.ModelBinderProviders.Insert(complexBinderIndex, new IocModelBinderProvider(loggerFactory));
This question is tagged with ASP.NET Core, so here's our solution for dotnet core 3.1.
Outline of our solution: TheProject needs to make ICustomerService available to an object created automatically in the request pipeline. Classes that need this are tagged with an interface, IUsesCustomerService. This interface is then checked by the Binder on object creation, and special case is handled.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
namespace TheProject.Infrastructure.DependencyInjection
{
/// <summary>
/// This is a simple pass through class to the binder class.
/// It gathers some information from the context and passes it along.
/// </summary>
public class TheProjectModelBinderProvider : IModelBinderProvider
{
public TheProjectModelBinderProvider()
{
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
ILoggerFactory ilogger;
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// The Binder that gets returned is a <ComplexTypeModelBinder>, but I'm
// not sure what side effects returning early here might cause.
if (!context.Metadata.IsComplexType || context.Metadata.IsCollectionType)
{
return null;
}
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
foreach (ModelMetadata property in context.Metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
ilogger = (ILoggerFactory)context.Services.GetService(typeof(ILoggerFactory));
return new TheProjectModelBinder(propertyBinders, ilogger);
}
}
/// <summary>
/// Custom model binder.
/// Allows interception of endpoint method to adjust object construction
/// (allows automatically setting properties on an object that ASP.NET creates for the endpoint).
/// Here this is used to make sure the <see cref="ICustomerService"/> is set correctly.
/// </summary>
public class TheProjectModelBinder : ComplexTypeModelBinder
{
public TheProjectModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory)
: base(propertyBinders, loggerFactory)
{
}
/// <summary>
/// Method to construct an object. This normally calls the default constructor.
/// This method does not set property values, setting those are handled elsewhere in the pipeline,
/// with the exception of any special properties handled here.
/// </summary>
/// <param name="bindingContext">Context.</param>
/// <returns>Newly created object.</returns>
protected override object CreateModel(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var customerService = (ICustomerService)bindingContext.HttpContext.RequestServices.GetService(typeof(ICustomerService));
bool setcustomerService = false;
object model;
if (typeof(IUsesCustomerService).IsAssignableFrom(bindingContext.ModelType))
{
setcustomerService = true;
}
// I think you can also just call Activator.CreateInstance here.
// The end result is an object that's constructed, but no properties are set yet.
model = base.CreateModel(bindingContext);
if (setcustomerService)
{
((IUsesCustomerService)model).SetcustomerService(customerService);
}
return model;
}
}
}
Then in the startup code, make sure to set AddMvcOptions.
public void ConfigureServices(IServiceCollection services)
{
// ...
// asp.net core 3.1 MVC setup
services.AddControllersWithViews()
.AddApplicationPart(assembly)
.AddRazorRuntimeCompilation()
.AddMvcOptions(options =>
{
IModelBinderProvider complexBinder = options.ModelBinderProviders.FirstOrDefault(x => x.GetType() == typeof(ComplexTypeModelBinderProvider));
int complexBinderIndex = options.ModelBinderProviders.IndexOf(complexBinder);
options.ModelBinderProviders.RemoveAt(complexBinderIndex);
options.ModelBinderProviders.Insert(complexBinderIndex, new Infrastructure.DependencyInjection.TheProjectModelBinderProvider());
});
}

Categories

Resources