Terry, meu amigo, você e eu devemos tomar uma bebida. Temos alguns problemas semelhantes.
1. Estrutura do projeto: concordo com Eduardo que a estrutura de pastas em um aplicativo MVC deixa algo a desejar. Você tem suas pastas padrão de Controladores, Modelos e Vistas. Mas a pasta Views é dividida em uma pasta diferente para cada Controlador, além de uma pasta compartilhada. E cada Views / ControllerName ou Views / Shared pode ser dividida em EditorTemplates e DisplayTemplates. Mas permite que você decida como organizar sua pasta Modelos (você pode fazer com ou sem subpastas e declarações adicionais de namespace).
Deus não permita que você esteja usando Áreas, que duplicam a estrutura de pastas Controladores, Modelos e Vistas de 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
Isso significa que, se você estiver trabalhando com algo como um WidgetController, precisará procurar em outras pastas para encontrar os respectivos WidgetViewModels, WidgetViews, WidgetEditorTemplates, WidgetDisplayTemplates, etc. essas convenções MVC. No que diz respeito a colocar um modelo, controlador e exibição na mesma pasta, mas com espaços para nome diferentes, evito isso porque uso o ReSharper. Irá sublinhar um espaço para nome que não corresponde à pasta em que a classe está localizada. Eu sei que poderia desativar esse recurso R #, mas ajuda em outras partes do projeto.
Para arquivos que não são de classe, o MVC fornece conteúdo e scripts prontos para uso. Tentamos manter todos os nossos arquivos estáticos / não compilados nesses locais, novamente, para seguir a convenção. Sempre que incorporamos uma biblioteca js que usa temas (imagens e ou css), todos os arquivos de tema ficam em algum lugar em / content. Para o script, apenas colocamos todos eles diretamente em / scripts. Originalmente, isso era para obter o JS intellisense do VS, mas agora que obtemos o JS intellisense do R #, independentemente da localização em / scripts, suponho que possamos nos afastar disso e dividir os scripts por pasta para melhor organizar. Você está usando o ReSharper? É ouro puro IMO.
Outro pequeno pedaço de ouro que ajuda muito na refatoração é o T4MVC. Usando isso, não precisamos digitar caminhos de string para nomes de áreas, nomes de controladores, nomes de ações e até arquivos de conteúdo e scripts. O T4MVC digita fortemente todas as seqüências mágicas para você. Aqui está uma pequena amostra de como a estrutura do seu projeto não importa tanto se você estiver usando o 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. Acesso a dados: Não tenho experiência com o PetaPoco, mas tenho certeza de que vale a pena conferir. Para seus relatórios complexos, você considerou o SQL Server Reporting Services? Ou você está executando em um banco de dados diferente? Desculpe, não sei ao certo o que exatamente você está pedindo. Usamos EF + LINQ, mas também colocamos certos conhecimentos sobre como gerar relatórios em classes de domínio. Portanto, temos o repositório de chamadas do serviço de domínio da chamada do controlador, em vez de ter o repositório de chamadas do controlador diretamente. Para relatórios ad-hoc, usamos o SQL Reporting Services, que novamente não é perfeito, mas nossos usuários gostam de poder trazer dados para o Excel facilmente, e o SSRS facilita isso para nós.
3. Organização do código do lado do cliente e renderização da interface do usuário: é aqui que acho que posso oferecer alguma ajuda. Veja uma página do livro de validação discreta do MVC e AJAX discreto. Considere isto:
<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 a função de sucesso do ajax por enquanto (mais sobre isso mais tarde). Você pode usar um único script para algumas de suas ações:
$('.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();
}
}
}
});
}
});
O código acima cuidará da confirmação, mostrando o botão giratório, mostrando a mensagem de espera e ocultando a mensagem giratória / de espera após a conclusão da chamada ajax. Você configura os comportamentos usando atributos data- *, como as bibliotecas discretas.
Questões gerais
- Cliente MVC vs. servidor MVC? Não tentei bibliotecificar as ações que você executou na função de sucesso, porque parece que seu controlador está retornando JSON. Se seus controladores estiverem retornando JSON, convém consultar o KnockoutJS. Knockout JS versão 2.0 foi lançada hoje . Ele pode ser conectado diretamente ao seu JSON, para que um clique observável possa vincular dados automaticamente aos seus modelos javascript. Por outro lado, se você não se importa em ter seus métodos de ação ajax retornando HTML em vez de JSON, eles podem retornar o UL já construído com seus filhos LI e você pode anexá-lo a um elemento usando data-myapp-response = "resultados". Sua função de sucesso ficaria assim:
success: function(html) {
var responseId = $(clicked).data('myapp-response');
if (responseId) {
$('#' + responseId).empty().html(html);
}
}
Para resumir minha melhor resposta para isso, se você deve retornar o JSON de seus métodos de ação, está pulando a Visualização do lado do servidor, para que isso realmente não seja o MVC do servidor - é apenas o MC. Se você retornar PartialViewResult com chamadas html para ajax, este será o servidor MVC. Portanto, se seu aplicativo precisar retornar dados JSON para chamadas ajax, use o MVVM do cliente como o KnockoutJS.
De qualquer maneira, eu não gosto do JS que você postou porque combina seu layout (tags html) com comportamento (carregamento de dados assíncrono). A escolha do MVC do servidor com visualizações html parciais ou do MVVM do cliente com dados puros do modelo de visualização JSON resolverá esse problema, mas a construção manual de DOM / HTML em javascript viola a separação de preocupações.
- Criação de arquivo Javascript Aparentemente, os recursos de minificação estão chegando no .NET 4.5 . Se você seguir a rota discreta, não deve haver nada impedindo o carregamento de todo o seu arquivo de script JS in 1. Seria cuidadoso ao criar arquivos JS diferentes para cada tipo de entidade; você acabará com a explosão do arquivo JS. Lembre-se, uma vez que seu arquivo de script é carregado, o navegador deve armazená-lo em cache para solicitações futuras.
- Consultas complexas Não considero complexos como paginação, classificação etc. Minha preferência é lidar com isso com URLs e lógica do lado do servidor, para tornar as consultas de banco de dados tão limitadas quanto necessárias. No entanto, como estamos implantados no Azure, a otimização de consultas é importante para nós. Por exemplo: /widgets/show-{pageSize}-per-page/page-{pageNumber}/sort-by-{sortColumn}-{sortDirection}/{keyword}
. EF e LINQ to Entities podem lidar com paginação e classificação com métodos como .Take (), .Skip (), .OrderBy () e .OrderByDescending (), para que você obtenha o que precisa durante a viagem ao banco de dados. Ainda não encontrei a necessidade de um clientlib, portanto, sinceramente, não sei muito sobre eles. Procure outras respostas para obter mais conselhos sobre isso.
- Projeto seda Nunca ouviu falar deste, terá que conferir. Sou um grande fã de Steve Sanderson, de seus livros, de BeginCollectionItem HtmlHelper e de seu blog. Dito isto, não tenho nenhuma experiência com o KnockoutJS em produção . Verifiquei seus tutoriais, mas tento não me comprometer com algo até que seja pelo menos a versão 2.0. Como eu mencionei, o KnockoutJS 2.0 foi lançado.
- Nível N Se por nível você quer dizer máquina física diferente, então não, acho que nada sai pelas janelas. Geralmente, em três níveis, você tem três máquinas. Portanto, você pode ter um cliente gordo como sua camada de apresentação, que é executada na máquina do usuário. O cliente gordo pode acessar uma camada de serviço, que é executada em um servidor de aplicativos e retorna XML ou qualquer outra coisa para o cliente gordo. E a camada de serviço pode obter seus dados de um servidor SQL em uma terceira máquina.
O MVC é uma camada, em uma camada. Seus controladores, modelos e visualizações fazem parte da sua Camada de apresentação, que é de 1 camada na arquitetura física. O MVC implementa o padrão Model-View-Controller, que é onde você pode ver camadas adicionais. No entanto, tente não pensar nesses três aspectos como camadas ou camadas. Tente pensar em todos os três como preocupações da camada de apresentação.
Atualização após comentário pres / bus / data
Ok, então você está usando camadas e camadas de forma intercambiável. Normalmente, uso o termo "camada" para divisões lógicas / projeto / montagem e camada para separação física de redes. Desculpe pela confusão.
Você encontrará algumas pessoas no campo do MVC que dizem que não deve usar os "Modelos" no MVC para o modelo de dados da entidade, nem usar os controladores para a lógica de negócios. Idealmente, seus modelos devem ser ViewModels específicos de exibição. Usando algo como o Automapper, você pega suas entidades do seu modelo de domínio e as DTO no ViewModels, esculpidas especificamente para serem usadas pela exibição.
Quaisquer regras de negócios também devem fazer parte do seu domínio, e você pode implementá-las usando serviços de domínio / padrão de fábrica / o que for apropriado em sua camada de domínio, não na camada de apresentação do MVC. Os controladores devem ser burros, embora não tão burros quanto os modelos, e devem dar responsabilidade ao domínio por qualquer coisa que exija conhecimento comercial. Os controladores gerenciam o fluxo de solicitações e respostas HTTP, mas qualquer coisa com valor comercial real deve estar acima da classe de pagamento do controlador.
Portanto, você ainda pode ter uma arquitetura em camadas, com o MVC como a camada de apresentação. É um cliente da sua camada de aplicativo, serviço ou domínio, dependendo de como você o arquitetou. Mas, em última análise, o modelo da sua entidade deve fazer parte do domínio, não dos modelos no MVC.