I have a view which displays a list of items, in each row I'm going to have a link, a link in each row points to different location So base upon a property called CertificationType
I have to generate an address, for example if the type equals to 'BirthCertificate' I need to fetch the address from a table called BirthCertificates
. So I decided to use a ChildActionOnly
action method because in my controller I can easily access to context and fetch the result, That why I didn't use HTML helpers.
View:
foreach(var item in Model)
{
//...
<a href="@Html.Action("GetFilePath", new { type = item.CertificateType, certificateFkId = item.CeritificateFk })"></a>
}
Controller:
public class MainController : Controller
{
//...
private readonly IUserCertificateService _certificateService;
public MainController(IUserCertificateService certificateService)
{
_certificateService = certificateService;
//...
}
[ChildActionOnly]
public ContentResult GetFilePath(CertificateType type, Guid certificateFkId)
{
var fileName = "";
switch (type)
{
case CertificateType.Shenasnameh:
{
var result = _birthCertificateService.GetBirthCertificateById(certificateFkId);
fileName = string.Format("/Content/Files/UserDocs/{0}/{1}", result.User.UserName, result.ShenasNamehFileName);
}
break;
//....
}
return Content(fileName);
}
}
It works like a charm. But I wanted to know Is there a better solution?
Any idea?
1 Answer 1
This would appear to be an abuse of child actions. Child Actions are meant to compose smaller items of your interface that you would need to re-use and load up part (albeit a small part) of the MVC pipeline to provide this functionality.
Furthermore, it is giving your Controller responsibility for a unit of work that you may eventually want to reuse elsewhere. Can you really not see a need to work out a full path to a file in the future?
You mention this:
in my controller I can easily access to context and fetch the result, That why I didn't use HTML helpers.
I can understand you not wanting to use HTML Helpers for this, as you need to make a request to a service to find out information about the user and the filename. Fine, but your reference to requiring access to a context, what context? Your service? This doesn't seem to warrant, at least in my eyes, a full controller action.
In my opinion, and this is an opinion-based "how is best to do this" question rather than code review, this method is best suited on your model. Extending your model to have a method that would return a path to the file that you can subsequently output seems like a much more logical place for this type of method. That way, should you need to get a path again in a different controller, you do not need to duplicate code and/or refactor the method into another location at a later date.
Another option would be to construct a ViewModel for each item in your (current) Model, which contains this URL for output. That way you can have a normal method on your controller, and call it whilst constructing the VM in the main Action.
Finally, have your models where the filename is sourced from a different column implement an interface that allows you to get a filename, and fallback to using URL Helpers.
Something as simple as below would suffice:
interface IFileNameProvider
{
GetFileName();
}
Each implementation would return the filename from its appropriate property as an attempt at model-level (not DB-level) normalisation (of course you could normalise the filename at DB level). That way you can use standard URL helpers after getting your filename from this method, and your username through a similar method.