Con esto muchos pensarán que es mejor tenerlo todo en un solo archivo, sin embargo, para mantener el código lo más “higiénico” se recomienda escribir pequeños módulos que sean fácilmente actualizables. Esto trae consigo problemas que antes no se tenían como puede ser la dependencia entre los diferentes módulos. Por ejemplo, si una página requiere una funcionalidad implementada en un determinado módulo (archivo js), y a su vez, dicho módulo depende de otro módulo (otro archivo js), entonces es necesario referenciar ambos módulos en la página (escribir 2 secciones <script>). Peor aún, muchas veces se tiende a escribir una pequeña funcionalidad de la página en una especie de componente (UserControl, Include, Partial o como le quiera llamar) que requiere de la presencia de algún script, en ese caso se debe estar atento e incluir las referencias que necesita en la página que está usando el componente.
Para evitar lo anterior surge el ClientResourceManager. La responsabilidad de este componente es administrar los recursos que se descargan al cliente, fundamentalmente archivos JavaScript y CSS. En la siguiente figura se muestra un diagrama de clases con la estructura básica de nuestro componente.
La clase ResourceDefinition servirá para almacenar instancias de los recursos que necesita la aplicación. Cada objeto de tipo ResourceDefinition deberá tener un nombre que lo identifique (propiedad Name). Entre sus propiedades más importantes están la URL del recurso, una lista con los nombres de los recursos de los que depende (propiedad Dependecies) y una propiedad que indica de que tipo es el recurso (propiedad Type, puede ser cualquier cadena, ej: script, css, etc. )
Por su parte, la clase ResourceManager se encarga de gestionar los recursos, los métodos más importantes son: RegisterResource encargado de almacenar las referencias a los recursos, GetResourcesOfType retorna una lista con los recursos de un determinado tipo y GetResourceByName que se encarga de buscar un determinado recurso según su nombre, el código de esta clase se muestra a continuación:
1: public class ResourceManager : IResourceManager
2: {
3: private readonly IDictionary<string, ResourceDefinition> resources = new Dictionary<string, ResourceDefinition>(StringComparer.OrdinalIgnoreCase);
5: public ResourceDefinition RegisterResource(string resourceType, string resourceName)
6: {
7: ResourceDefinition rd;
8: if (!resources.TryGetValue(resourceName, out rd))
9: {
10: rd = new ResourceDefinition(resourceName, resourceType);
11: resources.Add(resourceName, rd);
12: }
13: return rd;
14: }
15:
16: public IEnumerable<ResourceDefinition> GetResourcesOfType(string resourceType)
17: {
18: return resources.Values.Where(r => r.Type.Equals(resourceType, StringComparison.OrdinalIgnoreCase));
19: }
20:
21: public ResourceDefinition GetResourceByName(string resourceName)
22: {
23: ResourceDefinition rd;
24: resources.TryGetValue(resourceName, out rd);
25: return rd;
26: }
27:
28: public ResourceDefinition RegisterScript(string name)
29: {
30: return RegisterResource("script", name);
31: }
32:
33: public ResourceDefinition RegisterStylesheet(string name)
34: {
35: return RegisterResource("link", name);
36: }
37: }
1: public class UiResourceBootstrapperTask : IBootstrapperTask
2: {
3: private readonly IResourceManager resourceManager;
4:
5: public UiResourceBootstrapperTask(IResourceManager resourceManager)
6: {
7: this.resourceManager = resourceManager;
8: }
9:
10: public void Execute()
11: {
12: resourceManager.RegisterStylesheet(Styles.Default)
13: .SetUrl("Default.css");
14:
15: resourceManager.RegisterStylesheet(Styles.JqueryUi)
16: .SetUrl("themes/custom-theme/jquery-ui-1.8.16.custom.css")
17: .SetVersion("1.8.16");
18:
19: resourceManager.RegisterScript(Scripts.JQuery)
20: .SetUrl("jquery-1.6.2.min.js")
21: .SetVersion("1.6.2");
22:
23: resourceManager.RegisterScript(Scripts.JQueryUI)
24: .SetUrl("jquery-ui-1.8.14.min.js")
25: .SetVersion("1.8.14")
26: .SetDependencies(Scripts.JQuery, Styles.JqueryUi);
27:
28: resourceManager.RegisterScript(Scripts.JQueryValidate)
29: .SetUrl("jquery.validate.1.8.1.min.js")
30: .SetVersion("1.8.1")
31: .SetDependencies(Scripts.JQuery);
32:
33: resourceManager.RegisterScript(Scripts.JQueryValidateUnobtrusive)
34: .SetUrl("jquery.validate.unobtrusive.min.js")
35: .SetDependencies(Scripts.JQuery, Scripts.JQueryValidate);
36:
37: resourceManager.RegisterScript(Scripts.Popups)
38: .SetUrl("jquery.tools.min.js")
39: .SetVersion("1.2.6-dev")
40: .SetDependencies(Scripts.JQuery);
41:
42: resourceManager.RegisterScript(Scripts.Tooltips)
43: .SetUrl("jquery.tools.min.js")
44: .SetVersion("1.2.6-dev")
45: .SetDependencies(Scripts.JQuery);
46:
47: resourceManager.RegisterScript(Scripts.Tiger)
48: .SetUrl("tiger.js")
49: .SetDependencies(Scripts.JQuery, Scripts.JQueryUI);
50:
51: resourceManager.RegisterScript(Scripts.TigerLocalization)
52: .SetUrl("tiger.localization.js")
53: .SetDependencies(Scripts.JQuery, Scripts.Tiger);
54:
55: resourceManager.RegisterScript(Scripts.TigerDialogs)
56: .SetUrl("tiger.dialogs.js")
57: .SetDependencies(Scripts.JQuery, Scripts.Popups, Scripts.Tiger, Scripts.TigerLocalization);
58: }
59: }
Una vez que se han cargado los recursos nos queda lo fundamental, poder usar estos recursos desde nuestras vistas, controladores, etc. Con este propósito surge la clase RequireResources la cual administra los recursos que se van a descargar en cada petición, la implementación de esta clase es la siguiente:
1: public class RequiereResources : IRequiereResources
2: {
3: private readonly IResourceManager resourceManager;
4:
5: private readonly IDictionary<string, ResourceDefinition> loadedResocurces = new Dictionary<string, ResourceDefinition>(StringComparer.OrdinalIgnoreCase);
6:
7: public RequireSettings Settings { get; set; }
8:
9: public RequiereResources(IResourceManager resourceManager)
10: {
11: this.resourceManager = resourceManager;
12: Settings = new RequireSettings();
13: Settings.DebugMode = System.Web.HttpContext.Current.IsDebuggingEnabled;
14: Settings.Culture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
15: }
16:
17: public void Requiere(string resourceName)
18: {
19: if (loadedResocurces.ContainsKey(resourceName))
20: return;
21:
22: var resource = resourceManager.GetResourceByName(resourceName);
23: if (resource != null)
24: {
25: if (resource.Dependencies != null)
26: {
27: foreach (var d in resource.Dependencies)
28: {
29: Requiere(d);
30: }
31: }
32:
33: loadedResocurces.Add(resource.Name, resource);
34: }
35: }
36:
37: public IEnumerable<string> GetDistinctUrlOfType(string resourceType)
38: {
39: ISet<string> result = new HashSet<string>();
40:
41: foreach (var resource in loadedResocurces.Values)
42: {
43: if (resource.Type.Equals(resourceType, StringComparison.OrdinalIgnoreCase))
44: {
45: var url = resource.ResolveUrl(Settings);
46: if (!string.IsNullOrEmpty(url))
47: {
48: if (!result.Contains(url))
49: {
50: result.Add(url);
51: }
52: }
53: }
54: }
55:
56: return result;
57: }
58: }
Es hora de mezclarlo todo. En este caso, para que se pueda usar la administración de recursos desde las vistas (dejo al lector la posibilidad de otros escenarios como pueden ser controladores, helpers, etc.) se crea una clase de la cual deberán heredar todas las demás vistas de la aplicación, su código se muestra a continuación:
1: public abstract class RazorWebViewPage<TModel> : System.Web.Mvc.WebViewPage<TModel>
2: {
3: public IRequiereResources RequiereResources { get; private set; }
4:
5: public IScriptManager ScriptManager { get; private set; }
6:
7: public JqueryHelper Jquery { get; private set; }
8:
9: public override void InitHelpers()
10: {
11: base.InitHelpers();
12:
13: RequiereResources = ServiceLocator.Current.GetInstance<IRequiereResources>();
14: ScriptManager = ServiceLocator.Current.GetInstance<IScriptManager>();
15: }
16:
17: public void Requiere(string resourceName)
18: {
19: RequiereResources.Requiere(resourceName);
20: }
21:
22: public IEnumerable<string> RequieredStylesheets
23: {
24: get { return RequiereResources.GetDistinctUrlOfType("link"); }
25: }
26:
27: public IEnumerable<string> RequieredScripts
28: {
29: get { return RequiereResources.GetDistinctUrlOfType("script"); }
30: }
31: }
1: <pages pageBaseType="App.Core.Web.Mvc.ViewEngines.RazorWebViewPage">
1: public static class ResourceHelper
2: {
3: public static MvcHtmlString RenderAsScripts(this IEnumerable<string> urls)
4: {
5: StringBuilder sb = new StringBuilder();
6:
7: foreach (var url in urls)
8: {
9: TagBuilder tag = new TagBuilder("script");
10: tag.MergeAttributes(new Dictionary<string, string>{
11: {"src", url.StartsWith("http:") ? url : "/scripts/" + url},
12: {"type", "text/javascript"}
13: });
14: sb.Append(tag.ToString(TagRenderMode.Normal));
15: }
16:
17: return new MvcHtmlString(sb.ToString());
18: }
19:
20: public static MvcHtmlString RenderAsStylesheets(this IEnumerable<string> urls)
21: {
22: StringBuilder sb = new StringBuilder();
23:
24: foreach (var url in urls)
25: {
26: TagBuilder tag = new TagBuilder("link");
27: tag.MergeAttributes(new Dictionary<string, string>{
28: {"href", url.StartsWith("http:") ? url : "/styles/" + url},
29: {"rel", "stylesheet"},
30: {"type", "text/css"}
31: });
32: tag.MergeAttribute("type", "text/javascript");
33: sb.Append(tag.ToString(TagRenderMode.Normal));
34: }
35:
36: return new MvcHtmlString(sb.ToString());
37: }
38: }
Y ya está, todo listo, que comience finalmente el espectáculo. Este es el código de una posible MasterPage (o layout como se quiera) :
1: @model ViewModel
2: @{
3: Requiere(Styles.Default);
4: Requiere(Scripts.Tooltips);
5: Requiere(Scripts.Tiger);
6: Requiere(Scripts.TigerLocalization);
7: }
8: <!DOCTYPE html>
9: <html>
10: <head>
11: <title>@Html.Raw(ViewBag.Title)</title>
12: @RequieredStylesheets.RenderAsStylesheets()
13: </head>
14: <body>
15: <div class="section-main">
16: HTML Code....
17: </div>
18:
19: @RequieredScripts.RenderAsScripts()
20: <script type="text/javascript">
21: @Html.Raw(ScriptManager.GetScripts())
22: </script>
23: @RenderSection("scripts", false)
24: </body>
25: </html>
Y esto es todo, ahora podemos separar los archivos de código javascript (o css) y a la vez poder usarlos de manera más cómoda, otra cosa es que lo hagamos ;)
No hay comentarios:
Publicar un comentario