简体   繁体   中英

How to design InfoHelper for html title attribute

For Asp.net mvc core app: In one of my dbcontext tables I have records with HelpInfo (id as primary key, and string for helpInfo) that I want to use as html title attributes in my razor views, such as:

<div title='@I(12)'></div>

Where 12 is an example id of a HelpInfo record and @I should be a global accessible class method that fetches the corresponding helpInfo string.

I tried to implement King Kings approach such as:

The CustomView :

public abstract class CustomView<TModel>  : RazorPage<TModel>
{
    protected IInfo Info => Context.RequestServices.GetRequiredService<IInfo>();

    public Task<string> I(int code)
    {
        return Info.GetAsync(code);
    }
}

The Interface :

public interface IInfo
{
    Task<string> GetAsync(int code);
}

The Service :

public class Info : IInfo
{
    private readonly ApplicationDbContext context;
    public Info(ApplicationDbContext context)
    {
        this.context = context;
    }

    public async Task<string> GetAsync(int code)
    {
        var myRecord = await context.MyRecords.FindAsync(code);
        if (myRecord != null)
        {
            return myRecord.Info;
        }
        return null;
    }
}

In _ViewImports.cshtml I added @inherits CustomView<TModel>

In the view I used <div title='@(await I(12))'>Test</div>

When I load the view I get

No service for type '...Models.IInfo' has been registered

Any help to pin down the problem would be appreciated.

As I understand looks like you want something like @Html or @Url which are supported in the default Page and RazorPage . That's just a custom property exposed by the base page. So in your case, you need a custom view (for mvc) or a custom page (for razor pages). Here is an example of a custom view used for MVC:

public abstract class CustomView<TModel> : RazorPage<TModel>
{              
    //custom members can be declared in here
}

Based on your desired usage of I , it must be a method. So it can be a method exposed by some service injected in your base page. Suppose that interface is like this:

public interface IInfo {
     string Get(int code);
}

Now your custom page can implement the I method like this:

public abstract class CustomView<TModel> : RazorPage<TModel>
{      
    //NOTE: the custom page class does not support constructor injection
    //So we need to get the injected services via the Context like this.
    protected IInfo Info => Context.RequestServices.GetRequiredService<IInfo>();        
    public string I(int code){
       return Info.Get(code);
    }
}

To use your custom view, you need to use the directive @inherits in your view or better in the _ViewImports.cshtml so that you don't have to repeat that @inherits everywhere, like this:

@inherits CustomView<TModel>

Sometimes the base view is not applied (so the base members are not available) until you rebuild your project.

Now you can use the I in your views as what you desire, like this:

<div title="@I(12)"></div>

Note the I method returns a string in my example, you can also make it return an IHtmlContent (eg: HtmlString ) or whatever type you need for your requirement.

NOTE :

Technically you can use any services (including ones querying for data from database), but in such cases please ensure that the querying is as fast as possible and use async to have the best performance & avoid thread starvation. Here is an example of the IInfo service that queries from a database:

public interface IInfo {
     Task<string> GetAsync(int code);
}

public class Info : IInfo {
     //inject whatever your Info needs
     //here we just need some DbContext (used in EFCore)
     public Info(InfoDbContext dbContext){
        _infoDbContext = dbContext;
     }
     readonly InfoDbContext _infoDbContext;
     public async Task<string> GetAsync(int code){
         var info = await _infoDbContext....
         return info;
     }
}

The I method then should be async as well:

public Task<string> I(int code){
       return Info.GetAsync(code);
}

I hope that you know how to register a DbContext to use in your code (that's part of EFCore so you may have to learn more about that first). Now to use the async method, you call it in your view like this:

<div title="@(await I(12))"></div>

Again, I would try to avoid querying the db in such a helper like that. As I said, the helper's method can be called multiple times right in one same view so usually we have fast methods or use caching for the info it need. This is related to another feature called caching which has more to be put in one short answer, you can learn more about that.

Solution :

To my Startup.cs I added:

services.AddScoped<IInfo, Info>();

services.AddTransient<IInfo, Info>(); also does work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM