Terry, mi amigo, tú y yo deberíamos tomar una copa. Tenemos algunos problemas similares.
1. Estructura del proyecto: estoy de acuerdo con Eduardo en que la estructura de carpetas en una aplicación MVC deja algo que desear. Tiene sus carpetas estándar de Controladores, Modelos y Vistas. Pero luego la carpeta Vistas se divide en una carpeta diferente para cada Controlador, más una carpeta Compartida. Y cada Vistas / Nombre del controlador o Vistas / Compartidas se pueden desglosar en Plantillas de editor y Plantillas de pantalla. Pero le permite decidir cómo organizar su carpeta Modelos (puede hacerlo con o sin subcarpetas y declaraciones de espacio de nombres adicionales).
Dios no permita que esté utilizando Áreas, que duplican la estructura de carpetas de Controladores, Modelos y Vistas para cada área.
/Areas
/Area1Name
/Controllers
FirstController.cs
SecondController.cs
ThirdController.cs
/Models
(can organize all in here or in separate folders / namespaces)
/Views
/First
/DisplayTemplates
WidgetAbc.cshtml <-- to be used by views in Views/First
/EditorTemplates
WidgetAbc.cshtml <-- to be used by views in Views/First
PartialViewAbc.cshtml <-- to be used by FirstController
/Second
PartialViewDef.cshtml <-- to be used by SecondController
/Third
PartialViewMno.cshtml <-- to be used by ThirdController
/Shared
/DisplayTemplates
WidgetXyz.cshtml <-- to be used by any view in Area1
/EditorTemplates
WidgetXyz.cshtml <-- to be used by any view in Area1
PartialViewXyz.cshtml <-- to be used anywhere in Area1
_ViewStart.cshtml <-- area needs its own _ViewStart.cshtml
Web.config <-- put custom HTML Helper namespaces in here
Area1NameRegistration.cs <-- define routes for area1 here
/Area2Name
/Controllers
/Models
/Views
Area2NameRegistration.cs <-- define routes for area2 here
/Controllers
AccountController.cs
HomeController.cs
/Models
/Views
/Account
/DisplayTemplates
WidgetGhi.cshtml <-- to be used views in Views/Account
/EditorTemplates
WidgetGhi.cshtml <-- to be used views in Views/Account
PartialViewGhi.cshtml <-- to be used by AccountController
/Home
(same pattern as Account, views & templates are controller-specific)
/Shared
/DisplayTemplates
EmailAddress.cshtml <-- to be used by any view in any area
Time.cshtml <-- to be used by any view in any area
Url.cshtml <-- to be used by any view in any area
/EditorTemplates
EmailAddress.cshtml <-- to be used by any view in any area
Time.cshtml <-- to be used by any view in any area
Url.cshtml <-- to be used by any view in any area
_Layout.cshtml <-- master layout page with sections
Error.cshtml <-- custom page to show if unhandled exception occurs
_ViewStart.cshtml <-- won't be used automatically in an area
Web.config <-- put custom HTML Helper namespaces in here
Esto significa que si está trabajando con algo como un WidgetController, tiene que buscar en otras carpetas para encontrar los WidgetViewModels, WidgetViews, WidgetEditorTemplates, WidgetDisplayTemplates relacionados, etc. Estas convenciones MVC. En cuanto a poner un modelo, controlador y vista en la misma carpeta pero con diferentes espacios de nombres, evito esto porque uso ReSharper. Subrayará un espacio de nombres que no coincide con la carpeta donde se encuentra la clase. Sé que podría desactivar esta función R #, pero ayuda en otras partes del proyecto.
Para archivos que no son de clase, MVC le ofrece contenido y secuencias de comandos de fábrica. Intentamos mantener todos nuestros archivos estáticos / no compilados en estos lugares, nuevamente, para seguir la convención. Cada vez que incorporamos una biblioteca js que usa temas (imágenes y / o CSS), todos los archivos de temas van a algún lugar debajo de / content. Para el script, simplemente los colocamos directamente en / scripts. Originalmente, esto era para obtener JS intellisense de VS, pero ahora que obtenemos JS intellisense de R # independientemente de la ubicación en / scripts, supongo que podríamos desviarnos de eso y dividir los scripts por carpeta para organizar mejor. ¿Estás usando ReSharper? Es oro puro OMI.
Otra pequeña pieza de oro que ayuda mucho con la refactorización es T4MVC. Con esto, no necesitamos escribir rutas de cadena para nombres de área, nombres de controlador, nombres de acción, incluso archivos en contenido y scripts. T4MVC teclea todas las cadenas mágicas por ti. Aquí hay una pequeña muestra de cómo la estructura de su proyecto no importa tanto si está usando T4MVC:
// no more magic strings in route definitions
context.MapRoutes(null,
new[] { string.Empty, "features", "features/{version}" },
new
{
area = MVC.PreviewArea.Name,
controller = MVC.PreviewArea.Features.Name,
action = MVC.PreviewArea.Features.ActionNames.ForPreview,
version = "december-2011-preview-1",
},
new { httpMethod = new HttpMethodConstraint("GET") }
);
@* T4MVC renders .min.js script versions when project is targeted for release *@
<link href="@Url.Content(Links.content.Site_css)?r=201112B" rel="stylesheet" />
<script src="@Url.Content(Links.scripts.jquery_1_7_1_js)" type="text/javascript">
</script>
@* render a route URL as if you were calling an action method directly *@
<a href="@Url.Action(MVC.MyAreaName.MyControllerName.MyActionName
(Model.SomeId))">@Html.DisplayFor(m => m.SomeText)</a>
// call action redirects as if you were executing an action method
return RedirectToAction(MVC.Area.MyController.DoSomething(obj1.Prop, null));
2. Acceso a datos: no tengo experiencia con PetaPoco, pero estoy seguro de que vale la pena echarle un vistazo. Para sus informes complejos, ¿ha considerado los servicios de informes de SQL Server? O, ¿estás corriendo en un db diferente? Lo siento, no tengo claro qué es exactamente lo que estás pidiendo. Usamos EF + LINQ, pero también ponemos cierto conocimiento sobre cómo generar informes en las clases de dominio. Por lo tanto, tenemos un repositorio de llamadas de servicio de dominio de llamada de controlador, en lugar de tener un repositorio de llamadas de controlador directamente. Para los informes ad-hoc utilizamos SQL Reporting Services, que de nuevo no es perfecto, pero a nuestros usuarios les gusta poder incorporar datos a Excel fácilmente, y SSRS nos lo pone fácil.
3. Organización del código del lado del cliente y representación de la interfaz de usuario: aquí es donde creo que puedo ofrecerle ayuda. Tome una página del libro de MVC discreta validación y discreta AJAX. Considera esto:
<img id="loading_spinner" src="/path/to/img" style="display:none;" />
<h2 id="loading_results" style="display:none;">
Please wait, this may take a while...
</h2>
<div id="results">
</div>
<input id="doSomethingDangerous" class="u-std-ajax"
type="button" value="I'm feeling lucky"
data-myapp-confirm="Are you sure you want to do this?"
data-myapp-show="loading_spinner,loading_results"
data-myapp-href="blah/DoDangerousThing" />
Ignore la función de éxito de ajax por ahora (más sobre esto más adelante). Puede salirse con la suya con un solo script para algunas de sus acciones:
$('.u-std-ajax').click(function () {
// maybe confirm something first
var clicked = this;
var confirmMessage = $(clicked).data('myapp-confirm');
if (confirmMessage && !confirm(confirmMessage )) { return; }
// show a spinner? something global would be preferred so
// I dont have to repeat this on every page
// maybe the page should notify the user of what's going on
// in addition to the dialog?
var show = $(clicked).data('myapp-show');
if (show) {
var i, showIds = show.split(',');
for (i = 0; i < showIds.length; i++) {
$('#' + showIds[i]).show();
}
}
var url = $(clicked).data('myapp-href');
if (url) {
$.ajax({
url: url,
complete: function () {
// Need to hide the spinner, again would prefer to
// have this done elsewhere
if (show) {
for (i = 0; i < showIds.length; i++) {
$('#' + showIds[i]).hide();
}
}
}
});
}
});
El código anterior se encargará de la confirmación, mostrando la ruleta, mostrando el mensaje de espera y ocultando la ruleta / mensaje de espera después de que se complete la llamada ajax. Configura los comportamientos utilizando atributos data- *, como las bibliotecas discretas.
Preguntas generales
- Cliente MVC vs. servidor MVC? No traté de bibrarificar las acciones que realizó en la función de éxito porque parece que su controlador está devolviendo JSON. Si sus controladores están devolviendo JSON, es posible que desee ver KnockoutJS. La versión 2.0 de Knockout JS fue lanzada hoy . Se puede conectar directamente a su JSON, por lo que un clic observable puede vincular automáticamente los datos a sus plantillas de JavaScript. Por otro lado, si no le importa que sus métodos de acción ajax devuelvan HTML en lugar de JSON, pueden devolver el UL ya construido con sus elementos secundarios LI, y puede agregarlo a un elemento utilizando data-myapp-response = "resultados". Su función de éxito se vería así:
success: function(html) {
var responseId = $(clicked).data('myapp-response');
if (responseId) {
$('#' + responseId).empty().html(html);
}
}
Para resumir mi mejor respuesta para esto, si debe devolver JSON de sus métodos de acción, está omitiendo la Vista del lado del servidor, por lo que esto realmente no es MVC del servidor, es solo MC. Si devuelve PartialViewResult con html a llamadas ajax, este es el servidor MVC. Entonces, si su aplicación debe devolver datos JSON para llamadas ajax, use MVVM del cliente como KnockoutJS.
De cualquier manera, no me gusta el JS que publicaste porque combina tu diseño (etiquetas html) con el comportamiento (carga de datos asincrónica). Elegir MVC de servidor con vistas html parciales o MVVM de cliente con datos de modelos de vista JSON puros resolverá este problema, pero construir manualmente DOM / HTML en JavaScript viola la separación de preocupaciones.
- Creación de archivos Javascript Aparentemente, las características de minificación vienen en .NET 4.5 . Si sigue la ruta discreta, no debería haber nada que le impida cargar todo su JS en 1 archivo de script. Tendría cuidado al crear diferentes archivos JS para cada tipo de entidad, terminarás con una explosión de archivos JS. Recuerde, una vez que se carga su archivo de script, el navegador debe almacenarlo en caché para futuras solicitudes.
- Consultas complejas que no considero que tengan características como paginación, clasificación, etc., como complejas. Mi preferencia es manejar esto con URL y lógica del lado del servidor, para hacer que las consultas de db sean tan limitadas como sea necesario. Sin embargo, estamos implementados en Azure, por lo que la optimización de consultas es importante para nosotros. Por ejemplo: /widgets/show-{pageSize}-per-page/page-{pageNumber}/sort-by-{sortColumn}-{sortDirection}/{keyword}
. EF y LINQ to Entities pueden manejar la paginación y la clasificación con métodos como .Take (), .Skip (), .OrderBy () y .OrderByDescending (), para que obtenga lo que necesita durante el viaje db. Todavía no he encontrado la necesidad de un clientlib, así que honestamente no sé mucho sobre ellos. Busque otras respuestas para obtener más consejos al respecto.
- Proyecto seda Nunca he oído hablar de este, tendrá que echarle un vistazo. Soy un gran admirador de Steve Sanderson, sus libros, su BeginCollectionItem HtmlHelper y su blog. Dicho esto, no tengo ninguna experiencia con KnockoutJS en producción . He revisado sus tutoriales, pero trato de no comprometerme con algo hasta que sea al menos la versión 2.0. Como mencioné, se acaba de lanzar KnockoutJS 2.0.
- N-tier Si por nivel te refieres a una máquina física diferente, entonces no, no creo que nada salga por ninguna ventana. Generalmente 3 niveles significa que tiene 3 máquinas. Por lo tanto, es posible que tenga un cliente pesado como nivel de presentación, que se ejecuta en la máquina de un usuario. El cliente pesado puede acceder a un nivel de servicio, que se ejecuta en un servidor de aplicaciones y devuelve XML o lo que sea al cliente pesado. Y el nivel de servicio puede obtener sus datos de un servidor SQL en una tercera máquina.
MVC es una capa, en 1 nivel. Sus controladores, modelos y vistas son parte de su capa de presentación, que es 1 nivel en la arquitectura física. MVC implementa el patrón Modelo-Vista-Controlador, que es donde podría estar viendo capas adicionales. Sin embargo, trate de no pensar en estos 3 aspectos como niveles o capas. Trate de pensar en los 3 como preocupaciones de la capa de presentación.
Actualización después del comentario pres / bus / data
Bien, entonces estás usando niveles y capas de manera intercambiable. Usualmente uso el término "capa" para las divisiones lógicas / de proyecto / ensamblaje, y el nivel para la separación física de la red. Perdón por la confusion.
Encontrará bastantes personas en el campamento de MVC que dicen que no debe usar los "Modelos" en MVC para su modelo de datos de entidad, ni debe usar sus Controladores para la lógica de negocios. Idealmente, sus modelos deben ser ViewModels específicos de la vista. Usando algo como Automapper, tomas tus entidades de tu modelo de dominio y las DTO en ViewModels, esculpidas específicamente para su uso por la vista.
Cualquier regla de negocio también debe ser parte de su dominio, y puede implementarla utilizando servicios de dominio / patrón de fábrica / lo que sea apropiado en su capa de dominio, no en la capa de presentación MVC. Los controladores deben ser tontos, aunque no tan tontos como los modelos, y deben responsabilizar al dominio por cualquier cosa que requiera conocimiento comercial. Los controladores administran el flujo de solicitudes y respuestas HTTP, pero cualquier cosa con valor comercial real debe estar por encima de la calificación salarial del controlador.
Por lo tanto, aún puede tener una arquitectura en capas, con MVC como capa de presentación. Es un cliente de su capa de aplicación, capa de servicio o capa de dominio, dependiendo de cómo lo haya diseñado. Pero en última instancia, su modelo de entidad debe ser parte del dominio, no modelos en MVC.