ASP.NET MVC - trabalhando com o MySQL através do ADO.NET

Tecnologia herdada
Aviso: o ASP.NET MVC está obsoleto e o ADO.NET também. É recomendável usar o ASP.NET Core com ORM moderno. Mas se você estiver interessado, leia.

Já, provavelmente, três vezes chego ao ASP.NET MVC. Após dez anos com o ASP.NET WebForms, é um pouco difícil mudar para a tecnologia MVC, pois há tantas diferenças que é mais fácil listar o que essas tecnologias têm em comum - exceto as bibliotecas do .NET Framework. Não vou escrever aqui - o MVC é melhor ou pior que o WebForms, eles são bons e você pode criar um bom aplicativo nas duas tecnologias. Também deixo meus pensamentos sobre a necessidade de TDD comigo, embora os tenha.

E agora vou falar sobre a tarefa mais padrão - o trabalho usual com dados: visualizar a lista de registros em um formulário tabular, adicionar, alterar e excluir dados (operações CRUD). No entanto, em quase todos os livros e em muitas soluções da Internet para o ASP.NET MVC, por algum motivo, a opção é considerada exclusivamente por meio do ORM (Object Relation Mapping): Entity Framework (EF) ou LINQ for SQL. As tecnologias são excelentes, sem dúvida, finalmente, o programador pode não entender - mas como esse DBMS muito relacional (que ele provavelmente usa) geralmente funciona, e mesmo o SQL, em teoria, não é mais necessário: colocar na forma de EF e conectores para o DBMS se entenderão. "Aqui está a felicidade - não há mais beleza para ela." Mas para os programadores que não têm medo de trabalhar diretamente com o banco de dados por meio do mecanismo ADO.NET, muitas vezes não é claro - e por onde começar com o ASP.NET MVC em geral e se é necessário.

Além disso, para mim, por exemplo, no início, causou uma quebra violenta com a falta de um componente conveniente para exibir dados em uma tabela de grade. Entende-se que o próprio desenvolvedor deve implementar tudo isso ou obter algo adequado do gerenciador de pacotes. Se você, como eu, ficou mais do que satisfeito com o componente GridView no ASP.NET WebForms, então para o MVC é difícil encontrar algo mais ou menos comparável, exceto o Grid.mvc. Mas minha confiança nesses componentes não é suficiente para usá-los em um projeto suficientemente grande. Se forem usados, o programador começa a depender de outro desenvolvedor que, em primeiro lugar, não sabe escrever esse componente (é qualitativo?). Em segundo lugar, não se sabe quando e como será finalizado. Às vezes parece até possível expandir o componente e enxergá-lo ainda mais, mas, se o desenvolvedor o concluir, somos forçados a refazer nosso código novamente ou congelar a atualização do componente em uma determinada versão. E se o desenvolvedor corrigir uma vulnerabilidade, você ainda precisará atualizar para a nova versão. Até a transição para novas versões do conector MySQL causa alguns problemas, embora ainda esteja sendo desenvolvida por uma grande empresa, mas e as inúmeras "bicicletas" no gerenciador de pacotes Nuget?

Então, vamos tentar aprender a escrever no ASP.NET MVC, enquanto trabalhamos com dados processados ​​pelo MySQL DBMS. Todo o código pode ser obtido aqui neste endereço , abaixo deste código será parcialmente apresentado com pequenos links e explicações. E aqui você pode ver o aplicativo.

Crie um banco de dados no MySQL


CREATE SCHEMA `example`; USE `example`; CREATE TABLE `users` ( `UserID` int(4) unsigned NOT NULL AUTO_INCREMENT, `SupporterTier` enum('None','Silver','Gold','Platinum') NOT NULL DEFAULT 'None', `Loginname` varchar(255) NOT NULL, `LanguageID` int(4) unsigned NOT NULL DEFAULT '2', `Email` varchar(255) DEFAULT NULL, `LastLoginDate` datetime DEFAULT NULL, PRIMARY KEY (`UserID`), KEY `i_Loginname` (`Loginname`), KEY `i_Language_idx` (`LanguageID`), CONSTRAINT `i_Language` FOREIGN KEY (`LanguageID`) REFERENCES `languages` (`LanguageID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `languages` ( `LanguageID` int(4) unsigned NOT NULL AUTO_INCREMENT, `LanguageName` varchar(50) NOT NULL DEFAULT '', `Culture` varchar(10) DEFAULT NULL, PRIMARY KEY (`LanguageID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 

O banco de dados (DB) será simples e representado por apenas duas tabelas, com um relacionamento um para muitos no campo LanguageID . Dessa forma, vou complicar a situação pela necessidade de usar a lista suspensa de um dos idiomas para o usuário. Além disso, para complicar, para o usuário, também apresentaremos o campo SupporterTier , que determinará o nível de suporte do usuário por meio da enumeração. E, para usar quase todos os tipos de dados em nosso projeto, adicionamos o campo LastLoginDate do tipo "data / hora", que será preenchido pelo próprio aplicativo quando o usuário efetuar login (não refletido neste projeto).

Crie um projeto




Escolha "MVC". É possível usar “Vazio”, mas temos um treinamento, não um aplicativo real, portanto, isso nos ajudará imediatamente, sem gestos desnecessários, a integrar o Bootstrap e o JQuery em nosso aplicativo.



Já recebemos as pastas Conteúdo , Fontes , Scripts , bem como os arquivos BundleConfig.cs e FilterConfig.cs no diretório App_Start com os pacotes ASP.NET MVC e registros de filtros. Em um projeto vazio, há apenas registro de rotas no arquivo RouteConfig.cs . No Global.asax.cs, as chamadas de método descritas nesses arquivos também serão adicionadas:

 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); 

Montamos a infraestrutura e todo o pacote


Para trabalhar com o MySQL DBMS, adicione a biblioteca MySql.Data : manualmente, se o conector mysql-connector-net-8.0.18estiver instalado no computador ou no gerenciador de pacotes Nuget:



Adicione a configuração da cadeia de conexão ao MySQL DBMS no arquivo Web.config :

 <connectionStrings> <add name="example" providerName="MySql.Data.MySqlClient" connectionString="server=localhost;Port=3306;user id=develop;Password=develop;persistsecurityinfo=True;database=example;CharSet=utf8;SslMode=none" /> </connectionStrings> 

Adicione uma linha à seção <appSettings> com um link para a cadeia de conexão adicionada: <add key="ConnectionString" value="example" />
Adicionamos um novo diretório Domínio ao aplicativo, nele criamos uma nova classe Base estática (no arquivo Base.cs ), na qual acessamos esses parâmetros:

 public static class Base { private static string ConnectionString { get { return System.Configuration.ConfigurationManager.AppSettings["ConnectionString"]; } } public static string strConnect { get { return System.Configuration.ConfigurationManager.ConnectionStrings[ConnectionString].ConnectionString; } } } 

Eu gosto de ter uma certa classe base no aplicativo com links para parâmetros do aplicativo e algumas funções padrão que podem ser chamadas de todo o aplicativo.
O nome da cadeia de conexão é definido nas configurações do aplicativo, para que no futuro seja mais fácil trabalhar com a cadeia de conexão com o DBMS: alterne rapidamente entre diferentes bancos de dados e altere os parâmetros de conexão. Além disso, é conveniente usar o nome da string de conexão no parâmetro application para publicá-lo no Microsoft Azure - você pode definir o parâmetro para o serviço de aplicativo usado para publicação e determinar a string de conexão desejada, predefinida em <connectionStrings> . Em seguida, ao publicar, você não pode usar a transformação do arquivo web.config .

Também gosto de usar os valores do arquivo de recurso global no texto, para não substituí-los em vários lugares, se você precisar de repente. Por exemplo:



No arquivo de layout da página _Layout.cshtml (ele está localizado no diretório Views \ Shared e mais tarde usado em todas as páginas deste projeto), agora você pode usar essas variáveis ​​(consulte, por exemplo, Example_Users.Properties.Resources.Title ):

 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title – @Example_Users.Properties.Resources.Title</title> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") </head> <body> <div class="container body-content"> <h1 class="page-header"><a href="/">@Example_Users.Properties.Resources.Title</a></h1> @RenderBody() <hr /> <footer> <p> @DateTime.Now.Year – @Example_Users.Properties.Resources.Author</p> </footer> </div> @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/bootstrap") @RenderSection("scripts", required: false) </body> </html> 

Também neste arquivo, vemos o anexo da folha de estilos em cascata do Bootstrap conectado e da biblioteca de scripts JQuery. O conteúdo de todas as visualizações será gerado no local da chamada para a função RenderBody() .

M significa modelo


Adicione o arquivo UserClass.cs ao diretório Domínio :

 [DisplayName("User")] public class UserClass { [Key] [HiddenInput(DisplayValue=false)] public int UserID { get; set; } [Required(ErrorMessage="Please enter a login name")] [Display(Name = "Login")] public string Loginname { get; set; } public virtual LanguageClass Language { get; set; } [EmailAddress(ErrorMessage = "Please enter a valid email")] public string Email { get; set; } [UIHint("Enum")] [EnumDataType(typeof(Supporter))] [Required(ErrorMessage = "Please select supporter tier")] [Display(Name = "Supporter")] public Supporter SupporterTier { get; set; } [HiddenInput(DisplayValue = true)] [ScaffoldColumn(false)] [Display(Name = "Last login")] public DateTime? LastLoginDate { get; set; } [HiddenInput(DisplayValue = false)] public bool IsLastLogin { get { return LastLoginDate != null && LastLoginDate > DateTime.MinValue; } } public UserClass() {} public UserClass(int UserID, string Loginname, LanguageClass Language, string Email, DateTime? LastLoginDate, Supporter SupporterTier) { this.UserID = UserID; this.Loginname = Loginname; this.Language = Language; this.Email = Email; this.LastLoginDate = LastLoginDate; this.SupporterTier = SupporterTier; } } public enum Supporter { [Display(Name="")] None = 1, Silver = 2, Gold = 3, Platinum = 4 } 

E também o arquivo LanguageClass.cs no mesmo diretório:
 [DisplayName("Language")] public class LanguageClass { [Key] [HiddenInput(DisplayValue = false)] [Required(ErrorMessage = "Please select a language")] [Range(1, int.MaxValue, ErrorMessage = "Please select a language")] public int LanguageID { get; set; } [Display(Name = "Language")] public string LanguageName { get; set; } public LanguageClass() {} public LanguageClass(int LanguageID, string LanguageName) { this.LanguageID = LanguageID; this.LanguageName = LanguageName; } } 

Aqui você pode ver que as propriedades das classes repetem a estrutura da tabela Users e Languages no DBMS. Um enum Supporter criado para o tipo de enumeração, para que possa ser usado para a propriedade da classe SupporterTier de um campo semelhante na tabela de banco de dados. Para os campos UserID , LanguageID , é possível ver que eles são especificados como chave primária, assim como no banco de dados. O atributo [Key] é usado para isso.

Todos os outros atributos estão mais provavelmente relacionados a visualizações usando esta classe. E se vamos usar ajudantes para formar tags HTML para essas propriedades (que eu pessoalmente recomendaria), teremos que definir esses atributos com muito cuidado para obter o que precisamos. Em particular, eis o que era necessário para este projeto:

  • [DisplayName] - Usado como o nome de exibição da classe. Às vezes, pode ser útil, neste projeto eu adicionei especialmente o uso do auxiliar Html.DisplayNameForModel para demonstração.
  • [Display] com a propriedade Name - usada como o nome da propriedade da classe exibida na tela. Há também uma propriedade Order útil que permite que você ordene a sequência de exibição das propriedades da classe em um formulário usando helpers (por padrão, classificando pela ordem em que as propriedades da classe são definidas, portanto a propriedade Order não foi usada neste projeto).
  • [HiddenInput] com a propriedade DisplayValue . É usado para propriedades que nem precisam ser exibidas em formulários e listas ( DisplayValue=false , são desenhadas como tags de input com o tipo hidden ) ou para propriedades que ainda precisam ser exibidas, mas na forma de texto imutável ( DisplayValue=true , é desenhado como texto limpo, sem tags)
  • [ScaffoldColumn] - indica se o campo deve ser exibido nos Html.EditorForModel edição (por exemplo, Html.EditorForModel ). Se false , nem a descrição da propriedade da classe nem seu valor serão exibidos no formulário. [HiddenInput(DisplayValue = false)] não pode ser usado [HiddenInput(DisplayValue = false)] , porque nesse caso os valores dessa propriedade de classe não serão exibidos, não apenas nos formulários de entrada de informações, mas também nos displays de tabela. Nesse caso, isso era necessário para a propriedade LastLoginDate , que não é inserida manualmente, mas é preenchida em algum lugar automaticamente, mas ainda precisamos vê-la.
  • [Required] - para verificar se um valor foi inserido para uma propriedade de classe, com o texto da mensagem de erro na propriedade ErrorMessage e na propriedade AllowEmptyStrings permite inserir linhas em branco.
  • [EmailAddress] - essencialmente o mesmo atributo para verificar a exatidão do endereço de correspondência.

As classes do modelo de banco de dados estão prontas, passamos para as representações (as classes de modelos para representações serão descritas abaixo).

V - significa desempenho da vingança


No diretório Views , crie o diretório Users para nossos views. Todas as nossas visualizações usam o layout padrão (definido no arquivo _ViewStart.cshtml no diretório Views ) _Layout.cshtml localizado no diretório Views \ Shared . Crie uma exibição de Index (arquivo Index.cshtml no diretório Usuários ):



E escreva o código:

 @model Example_Users.Models.UsersGrid @{ ViewBag.Title = "Users page"; } @using (@Html.BeginForm()) { <div> <h3>Users list:</h3> @if (TempData["message"] != null) {<div class="text-success">@TempData["message"]</div>} @if (TempData["error"] != null) {<div class="text-warning"><span class="alert">ERROR:</span> @TempData["error"]</div>} @Html.Partial("List") <p> <input type="submit" name="onNewUser" value="New user" /> @*@Html.ActionLink("New user", "New", "Users")*@ </p> </div> } 

Se quisermos que essa exibição inicie por padrão, faça uma alteração no Default no arquivo RouteConfig.cs :

 routes.MapRoute(name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Users", action = "Index", id = UrlParameter.Optional }); 

Na exibição em si, você precisa prestar atenção à linha com Html.Partial("List") . É necessário desenhar nesse local uma exibição parcial geral separada e especial localizada no arquivo List.cshtml no diretório Views \ Shared . Na verdade, é uma tabela de grade para exibir dados da tabela de banco de dados de nossos users :

 @model Example_Users.Models.UsersGrid @using Example_Users.Domain <div class="table-responsive"> <table class="table table-bordered table-hover"> <thead> <tr> <th>@Html.ActionLink(Html.DisplayNameFor(m => Model.Users.First().Loginname).ToString(), "Index", Request.QueryString.ToRouteValueDictionary("sortOrder", Model.SortingInfo.NewOrder(Html.NameFor(m => Model.Users.First().Loginname).ToString()))) @Html.SortIdentifier(Model.SortingInfo.currentSort, Html.NameFor(m => Model.Users.First().Loginname).ToString()) </th> <th>@Html.ActionLink(Html.DisplayNameFor(m => Model.Users.First().Language.LanguageName).ToString(), "Index", Request.QueryString.ToRouteValueDictionary("sortOrder", Model.SortingInfo.NewOrder(Html.NameFor(m => Model.Users.First().Language).ToString()))) @Html.SortIdentifier(Model.SortingInfo.currentSort, Html.NameFor(m => Model.Users.First().Language).ToString()) </th> <th>@Html.ActionLink(Html.DisplayNameFor(m => Model.Users.First().Email).ToString(), "Index", Request.QueryString.ToRouteValueDictionary("sortOrder", Model.SortingInfo.NewOrder(Html.NameFor(m => Model.Users.First().Email).ToString()))) @Html.SortIdentifier(Model.SortingInfo.currentSort, Html.NameFor(m => Model.Users.First().Email).ToString()) </th> <th>@Html.ActionLink(Html.DisplayNameFor(m => Model.Users.First().SupporterTier).ToString(), "Index", Request.QueryString.ToRouteValueDictionary("sortOrder", Model.SortingInfo.NewOrder(Html.NameFor(m => Model.Users.First().SupporterTier).ToString()))) @Html.SortIdentifier(Model.SortingInfo.currentSort, Html.NameFor(m => Model.Users.First().SupporterTier).ToString()) </th> <th>@Html.ActionLink(Html.DisplayNameFor(m => Model.Users.First().LastLoginDate).ToString(), "Index", Request.QueryString.ToRouteValueDictionary("sortOrder", Model.SortingInfo.NewOrder(Html.NameFor(m => Model.Users.First().LastLoginDate).ToString()))) @Html.SortIdentifier(Model.SortingInfo.currentSort, Html.NameFor(m => Model.Users.First().LastLoginDate).ToString()) </th> </tr> </thead> <tbody> @foreach (var item in Model.Users) { <tr> <td>@Html.ActionLink(item.Loginname, "Edit", "Users", new { UserID = item.UserID }, null)</td> <td>@Html.DisplayFor(modelitem => item.Language.LanguageName)</td> <td>@Html.DisplayFor(modelitem => item.Email)</td> <td class="@Html.DisplayFor(modelitem => item.SupporterTier)">@if (item.SupporterTier != Supporter.None) {@Html.DisplayFor(modelitem => item.SupporterTier);}</td> <td>@if (item.IsLastLogin) {@Html.DisplayFor(modelitem => item.LastLoginDate)}</td> </tr> } </tbody> </table> </div> @if (Model.PagingInfo.totalPages > 1) { <ul class="pagination"> @Html.PageLinks(Model.PagingInfo, x => Url.Action("Index", new { page = x, sortOrder = Model.SortingInfo.currentSort })) </ul> } 

Pode-se observar que, no cabeçalho da tabela de dados, os auxiliares Html.DisplayNameFor são Html.DisplayNameFor para exibir os nomes das colunas e, para isso, é necessário especificar uma referência à propriedade do objeto de classe. Como ao formar o título da tabela, temos apenas um objeto Model.Users , que é uma lista de objetos do tipo UserClass , precisamos usar o seguinte método: selecione a primeira linha desta lista como um objeto da classe UserClass . Por exemplo, para o nome de usuário: Model.Users.First().Loginname . Como o atributo Loginname da classe Users possui o atributo [Display(Name = "Login")] , ele será exibido "Login" no nome da coluna:



O que mais é interessante na exibição de List ? O bloco foreach , é claro, renderiza objetos da classe UserClass que estão na lista Users recebidos do controlador. E os PagingInfo e PagingInfo em nosso modelo UsersGrid são interessantes aqui. E precisamos desses objetos para organizar a classificação dos dados (usada no cabeçalho da tabela nas tags <th> ) e organizar a paginação das informações (usadas na parte inferior da página, abaixo da tabela). É por isso que não usamos como modelo uma lista pura de objetos do tipo IEnumerable<UserClass> . E como modelo, usamos a classe UsersGrid , localizada no arquivo UsersGrid.cs no diretório Model .

 public class UsersGrid { public IEnumerable<UserClass> Users { get; set; } public PagingInfo PagingInfo { get; set; } public SortingInfo SortingInfo { get; set; } } 

E as classes SortingInfo e SortingInfo elas próprias no arquivo GridInfo.cs no mesmo local.

 public class PagingInfo { //     public int totalItems { get; set; } //     public int itemsPerPage { get; set; } //   public int currentPage { get; set; } //         public int showPages { get; set; } //   public int totalPages { get { return (int)Math.Ceiling((decimal)totalItems / itemsPerPage); } } //           public int pagesSide { get { return (int)Math.Truncate((decimal)showPages / 2); } } } public class SortingInfo { //  ,     public string currentOrder { get; set; } //   public SortDirection currentDirection { get; set; } //      public string currentSort { get { return currentDirection != SortDirection.Descending ? currentOrder : currentOrder + "_desc"; } } //       (      -   ) public string NewOrder(string columnName) { return columnName == currentOrder && currentDirection != SortDirection.Descending ? columnName + "_desc" : columnName; } } 

E para uso em visualizações, foram adicionados auxiliares especiais ao arquivo GridHelpers.cs (diretório HtmlHelpers ):

 public static class GridHelpers { //     1 ... 3 4 5 ... Last public static MvcHtmlString PageLinks(this HtmlHelper html, PagingInfo pagingInfo, Func<int, string> pageUrl) { StringBuilder result = new StringBuilder(); if (pagingInfo.currentPage > pagingInfo.pagesSide + 1) {//   TagBuilder li = new TagBuilder("li"); li.AddCssClass("page-item"); TagBuilder tag = new TagBuilder("a"); tag.MergeAttribute("href", pageUrl(1)); tag.InnerHtml = "1"; li.InnerHtml = tag.ToString(); result.Append(li.ToString()); } int page1 = pagingInfo.currentPage - pagingInfo.pagesSide; int page2 = pagingInfo.currentPage + pagingInfo.pagesSide; if (page1 < 1) { page2 = page2 - page1 + 1; page1 = 1; } if (page2 > pagingInfo.totalPages) page2 = pagingInfo.totalPages; if (page1 > 2) {// ... TagBuilder li = new TagBuilder("li"); li.AddCssClass("page-item"); TagBuilder tag = new TagBuilder("span"); tag.InnerHtml = "..."; tag.AddCssClass("page-item"); tag.AddCssClass("disabled"); li.InnerHtml = tag.ToString(); result.Append(li.ToString()); } for (int i = page1; i <= page2; i++) {//  TagBuilder li = new TagBuilder("li"); li.AddCssClass("page-item"); if (i == pagingInfo.currentPage) li.AddCssClass("active"); TagBuilder tag = new TagBuilder("a"); tag.AddCssClass("page-link"); tag.MergeAttribute("href", pageUrl(i)); tag.InnerHtml = i.ToString(); li.InnerHtml = tag.ToString(); result.Append(li.ToString()); } if (page2 < pagingInfo.totalPages) {// ...    TagBuilder li = new TagBuilder("li"); li.AddCssClass("page-item"); TagBuilder tag = new TagBuilder("span"); tag.InnerHtml = "..."; tag.AddCssClass("page-item"); tag.AddCssClass("disabled"); li.InnerHtml = tag.ToString(); result.Append(li.ToString()); li = new TagBuilder("li"); li.AddCssClass("page-item"); tag = new TagBuilder("a"); tag.MergeAttribute("href", pageUrl(pagingInfo.totalPages)); tag.InnerHtml = pagingInfo.totalPages.ToString(); li.InnerHtml = tag.ToString(); result.Append(li.ToString()); } return MvcHtmlString.Create(result.ToString()); } public static IHtmlString SortIdentifier(this HtmlHelper htmlHelper, string sortOrder, string field) { if (string.IsNullOrEmpty(sortOrder) || (sortOrder.Trim() != field && sortOrder.Replace("_desc", "").Trim() != field)) return null; string glyph = "glyphicon glyphicon-chevron-up"; if (sortOrder.ToLower().Contains("desc")) { glyph = "glyphicon glyphicon-chevron-down"; } var span = new TagBuilder("span"); span.Attributes["class"] = glyph; return MvcHtmlString.Create(span.ToString()); } public static RouteValueDictionary ToRouteValueDictionary(this NameValueCollection collection, string newKey, string newValue) { var routeValueDictionary = new RouteValueDictionary(); foreach (var key in collection.AllKeys) { if (key == null) continue; if (routeValueDictionary.ContainsKey(key)) routeValueDictionary.Remove(key); routeValueDictionary.Add(key, collection[key]); } if (string.IsNullOrEmpty(newValue)) { routeValueDictionary.Remove(newKey); } else { if (routeValueDictionary.ContainsKey(newKey)) routeValueDictionary.Remove(newKey); routeValueDictionary.Add(newKey, newValue); } return routeValueDictionary; } } 

Como uma grade com dados sem classificação e sem informações de paginação é uma coisa bastante inútil e não existe um auxiliar padrão para uma tabela de dados inteira no ASP.NET MVC, você deve criá-lo você mesmo (ou criar um criado por outra pessoa). Nesse caso, espiei várias implementações nos livros e soluções ASP.NET MVC apresentados na Internet. Além disso, por algum motivo, as soluções que combinam pelo menos classificação e paginação de dados não são de todo ou não foram encontradas. Eu tive que entender tudo isso, combinar e modificá-lo para um estado normal. Por exemplo, a paginação nessas implementações geralmente não fornece a saída de uma lista mais ou menos longa de páginas - bem, e se houver milhares de páginas? Dou um exemplo de exibição para a solução apresentada acima:



Também precisamos de visualizações para criar e modificar dados. Vista para criar um objeto do tipo UserClass :



E o código de exibição ficará assim:

 @model Example_Users.Models.UserModel @{ ViewBag.Title = "New " + Html.DisplayNameForModel().ToString().ToLower(); } <h2>@ViewBag.Title</h2> @using (@Html.BeginForm("New", "Users", FormMethod.Post)) { @Html.EditorFor(m => m.User); @Html.LabelFor(m => Model.User.Language)<br /> @Html.DropDownListFor(m => Model.User.Language.LanguageID, Model.SelectLanguages()) <span/>@Html.ValidationMessageFor(m => Model.User.Language.LanguageID)<br /> <br /> <p><input type="submit" name="action" value="Add" /> <input type="button" onclick="history.go(-1)" value="Cancel" /></p> @*<p>@Html.ActionLink("Back to list", "Index")</p>*@ } 

Essa visualização mostra, por exemplo, o uso do auxiliar Html.EditorFor como um meio de gerar tags para editar todas as propriedades dos objetos na classe UserClass . É exibido da seguinte maneira:



Essa visualização usa a classe UserModel como modelo, não a UserClass diretamente. A própria classe UserModel localizada no arquivo UserModel.cs no diretório Models :

 public class UserModel { public UserClass User { get; set; } private IList<LanguageClass> Languages { get; set; } public UserModel() {} public UserModel(UserClass user, IList<LanguageClass> languages) { this.User = user; this.Languages = languages; } public IEnumerable<SelectListItem> SelectLanguages() { if (Languages != null) { return new SelectList(Languages, "LanguageID", "LanguageName"); } return null; } } 

O objeto UserClass si e uma lista adicional de objetos do tipo LanguageClass incluídos nesta classe. Precisávamos dessa lista para criar uma lista suspensa de idiomas, com o idioma do usuário atual selecionado: @Html.DropDownListFor(m => Model.User.Language.LanguageID, Model.SelectLanguages(), "")

Esse auxiliar usa a chamada para a função SelectLanguages() , que converte a lista de idiomas em um objeto do tipo SelectList com os parâmetros de identificador e nome da linha já definidos. Colocar a geração desse objeto na visualização seria errado, porque a idéia era não saber sobre essas ligações aos nomes dos campos. SelectList , é possível, é claro, gerar imediatamente um SelectList pronto no controlador, mas eu gosto da opção com uma lista particular de objetos de classe de domínio e uma função mais.

Para gerar uma lista suspensa, precisamos usar auxiliares separados, porque o auxiliar Html.EditorFor(m => m.User) não gerará marcação de edição para um objeto incorporado do tipo LanguageClass (isso pode ser ignorado escrevendo um modelo geral para listas suspensas, mas aqui nós nós não faremos isso ...).

E, como usamos um objeto da classe UserClass , que inclui outro objeto da classe UserClass , não poderemos usar o auxiliar Html.EditorForModel() , porque os auxiliares não são recursivos e não funcionarão nessa situação, portanto, o auxiliar Html.EditorFor() é usado Html.EditorFor() para o objeto User .

Também quero prestar atenção à tag comentada: Html.ActionLink("Back to list", "Index")

geralmente, um retorno da exibição para edição na lista de dados geralmente é implementado dessa maneira. De fato, em primeiro lugar, na minha opinião, parece estranho - quando você usa botões de tipo em um formulário button, e o botão de retorno é, por algum motivo, implementado por um link. Em segundo lugar, se usarmos a classificação e a saída página por página - teremos que ser sábios ao retornar à mesma página na mesma exibição - e transmitir a exibição não apenas ao objeto UserClass, mas também aos parâmetros para retornar à página. Existe uma maneira muito mais simples - use a opção com um botão como:/>, que enviará o usuário de volta à página de histórico do navegador. Aqui, é claro, existem nuances (por exemplo, você já tentou salvar o objeto nesta página, ele não funcionou e agora - você já precisa clicar duas vezes no botão Cancelar), mas essa opção como um todo funciona bem.

E a visão para editar um objeto do tipo UserClass:



E seu código:

 @model Example_Users.Models.UserModel @{ ViewBag.Title = "Edit " + Html.DisplayNameForModel().ToString().ToLower(); } <h2>@ViewBag.Title @Model.User.Loginname</h2> @using (@Html.BeginForm("Edit", "Users", FormMethod.Post)) { @Html.HiddenFor(m => Model.User.UserID) <div> @Html.LabelFor(m => Model.User.Loginname) @Html.EditorFor(m => Model.User.Loginname) @Html.ValidationMessageFor(m => Model.User.Loginname) </div> <div> @Html.LabelFor(m => Model.User.Language) @Html.DropDownListFor(m => Model.User.Language.LanguageID, Model.SelectLanguages()) @Html.ValidationMessageFor(m => Model.User.Language.LanguageID) </div> <div> @Html.LabelFor(m => Model.User.Email) @Html.EditorFor(m => Model.User.Email) @Html.ValidationMessageFor(m => Model.User.Email) </div> <div> @Html.LabelFor(m => Model.User.SupporterTier) @Html.EditorFor(m => Model.User.SupporterTier) @*@Html.EnumDropDownListFor(m => Model.User.SupporterTier)*@ @*@Html.DropDownListFor(m => m.Model.User.SupporterTier, new SelectList(Enum.GetNames(typeof(Example_Users.Domain.Supporter))))*@ @Html.ValidationMessageFor(m => Model.User.SupporterTier) </div> <br /> <p><input type="submit" name="action" value="Save" /> <input type="submit" name="action" value="Remove" onclick="javascript:return confirm('Are you sure?');" /> <input type="submit" name="action" value="Cancel" /></p> } 

E nessa visão, é apresentada a opção de usar ajudantes diferentes para gerar tags separadamente para cada propriedade desejada de um objeto de classe. Nesse caso, você já pode fazer a exibição de uma maneira um pouco diferente e mais bonita:



e nessa exibição, outra maneira é usada para retornar à lista. Em vez de usar um botão como: />o mesmo botão é usado como os outros botões de ação ( Salvar , Remover ): />e o retorno é realizado dentro do método do controlador que processa as ações desses botões (consulte a implementação do método abaixo).

Enum


E havia uma nuance associada ao uso de uma propriedade de classe do tipo enum . O fato é que, se apenas usarmos o auxiliar Html.EditorFor(), o campo de entrada de informações de texto (tag de tipo <input type=”text”/>) será exibido no formulário , mas na verdade precisamos de um campo com uma escolha entre um conjunto de valores (ou seja, uma tag <select>com um conjunto de opções <option>).

1. linha recta é resolvido usando o tipo de ajudante Html.DropDownListFor()ou Html.ListBoxFor(), por exemplo, no nosso caso @Html.DropDownListFor(m => m.Model.User.SupporterTier, new SelectList(Enum.GetNames(typeof(Example_Users.Domain.Supporter)))). Existem dois pontos negativos - cada vez que ele deve ser prescrito individualmente para o ajudante Html.EditorForModel()ou Html.EditorFor()não funciona.

2. Você pode criar um modelo de tipo personalizado Editor. Crie o arquivo Supporter.cshtml na pasta Views \ Shared \ EditorTemplates :

 @model Example_Users.Domain.Supporter @Html.DropDownListFor(m => m, new SelectList(Enum.GetNames(Model.GetType()), Model.ToString())) 

, Html.EditorFor(m => Model.SupporterTier) , Html.EditorFor(m => Model.SupporterTier, "Supporter") . -, [UIHint("Supporter")] . , Html.EditorForModel()Html.EditorFor(m => Model.SupporterTier, "Supporter") .

- , ( New ) Html.EditorFor() Html.EditorForModel() : « , , NULL, «Example_Users.Domain.Supporter», NULL. » – , . .

3. Html.EnumDropDownListFor() , . , , , . «»: Html.EditorForModel() Html.EditorFor() , , Html.EnumDropDownListFor() . , , – [UIHint] , [DataType] [EnumDataType] . , None , , , Supporter.

4. Como resultado, criei uma opção de solução encontrada na Internet : criar um modelo comum para transferências. Crie o arquivo Enum.cshtml na pasta Views \ Shared \ EditorTemplates :

 @using System.ComponentModel.DataAnnotations @model Enum @{ Func<object, string> GetDisplayName = o => { var result = null as string; var display = o.GetType() .GetMember(o.ToString()).First() .GetCustomAttributes(false) .OfType<DisplayAttribute>() .LastOrDefault(); if (display != null) result = display.GetName(); return result ?? o.ToString(); }; var values = Enum.GetValues(ViewData.ModelMetadata.ModelType).Cast<object>() .Select(v => new SelectListItem { Selected = v.Equals(Model), Text = GetDisplayName(v), // v.ToString(), Value = v.ToString() }); } @Html.DropDownList("", values) 

Aqui, em geral, tudo saiu bem: o modelo funciona muito bem sempre que possível. Você ainda [UIHint("Enum")]não pode adicionar. Além disso, este modelo geral lê o atributo [Display(Name)]para valores de enumeração usando uma função especial.

C significa controlador


Adicione o arquivo UsersController.cs ao diretório Controladores .

 public class UsersController : Controller { public int pageSize = 10; public int showPages = 15; public int count = 0; //    public ViewResult Index(string sortOrder, int page = 1) { string sortName = null; System.Web.Helpers.SortDirection sortDir = System.Web.Helpers.SortDirection.Ascending; sortOrder = Base.parseSortForDB(sortOrder, out sortName, out sortDir); UsersRepository rep = new UsersRepository(); UsersGrid users = new UsersGrid { Users = rep.List(sortName, sortDir, page, pageSize, out count), PagingInfo = new PagingInfo { currentPage = page, itemsPerPage = pageSize, totalItems = count, showPages = showPages }, SortingInfo = new SortingInfo { currentOrder = sortName, currentDirection = sortDir } }; return View(users); } [ReferrerHold] [HttpPost] public ActionResult Index(string onNewUser) { if (onNewUser != null) { TempData["referrer"] = ControllerContext.RouteData.Values["referrer"]; return View("New", new UserModel(new UserClass(), Languages())); } return View(); } [ReferrerHold] public ActionResult New() { TempData["referrer"] = ControllerContext.RouteData.Values["referrer"]; return View("New", new UserModel(new UserClass(), Languages())); } [HttpPost] public ActionResult New(UserModel model) { if (ModelState.IsValid) { if (model.User == null || model.User.Language == null || model.User.Language.LanguageID == 0) RedirectToAction("Index"); UsersRepository rep = new UsersRepository(); if (rep.AddUser(model.User)) TempData["message"] = string.Format("{0} has been added", model.User.Loginname); else TempData["error"] = string.Format("{0} has not been added!", model.User.Loginname); if (TempData["referrer"] != null) return Redirect(TempData["referrer"].ToString()); return RedirectToAction("Index"); } else { model = new UserModel(model.User, Languages()); // -          model.Languages,   return View(model); } } [ReferrerHold] public ActionResult Edit(int UserID) { UsersRepository rep = new UsersRepository(); UserClass user = rep.FetchByID(UserID); if (user == null) return HttpNotFound(); TempData["referrer"] = ControllerContext.RouteData.Values["referrer"]; return View(new UserModel(user, Languages())); } [HttpPost] public ActionResult Edit(UserModel model, string action) { if (action == "Cancel") { if (TempData["referrer"] != null) return Redirect(TempData["referrer"].ToString()); return RedirectToAction("Index"); } if (ModelState.IsValid) { if (model.User == null || model.User.Language == null || model.User.Language.LanguageID == 0) RedirectToAction("Index"); UsersRepository rep = new UsersRepository(); if (action == "Save") { if (rep.ChangeUser(model.User)) TempData["message"] = string.Format("{0} has been saved", model.User.Loginname); else TempData["error"] = string.Format("{0} has not been saved!", model.User.Loginname); } if (action == "Remove") { if (rep.RemoveUser(model.User)) TempData["message"] = string.Format("{0} has been removed", model.User.Loginname); else TempData["error"] = string.Format("{0} has not been removed!", model.User.Loginname); } if (TempData["referrer"] != null) return Redirect(TempData["referrer"].ToString()); return RedirectToAction("Index"); } else { model = new UserModel(model.User, Languages()); return View(model); } } public IList<LanguageClass> Languages() { IList<LanguageClass> languages = new List<LanguageClass>(); LanguagesRepository rep = new LanguagesRepository(); languages = rep.List(); return languages; } } 

:

1) Index

: public ViewResult Index(string sortOrder, int page = 1)

sortOrder page . page - , sortOrder , SQL- . ( , Base.cs ): sortOrder = Base.parseSortForDB(sortOrder, out sortName, out sortDir);

, UsersRepository ( , ), List UsersGridda lista real de usuários e das informações necessárias para organizar a paginação das informações e a classificação. Além disso, o valor de totalItem é obtido quando o método é chamado List, simultaneamente com a criação do objeto de classe UsersGrid. Além disso, esse objeto é transferido para exibição em representação.

Há também outro método Index com o atributo [HttpPost]que precisamos para trabalhar com o botão, Newna visualização Index: public ActionResult Index(string onNewUser)

O parâmetro de entrada tem onNewUseralgo em comum com o elemento />na visualização Indexe, quando clicamos nesse botão, passa o valor para a função que comparamos com nula . Se os botões de tipo submitestivessem na exibiçãoIndexvários, você precisaria verificar o próprio valor (nesse caso, o valor seria "Novo usuário").

Após a verificação, formamos um objeto de classe UserModel, consistindo em um novo objeto UserClasse uma lista de idiomas para selecioná-los na lista suspensa e passá-los para a apresentação New. A lista de idiomas é obtida no repositório LanguageRepositorychamando o método da Listseguinte maneira:

 public IList<LanguageClass> Languages() { LanguagesRepository rep = new LanguagesRepository(); return rep.List(); } 

2) Métodos New

Um método Newsem um parâmetro é criado para dar um clique no link Novo Usuário para uma linha comentada Html.ActionLink("New user", "New", "Users")na exibição Index. A ação (abrindo a visualização New) é executada de maneira semelhante à anterior, mas não há verificação no botão pressionado, porque clicar no link está sendo elaborado.

O método Newcom o parâmetro de modelo do tipo UserModelpara processar o evento de envio de dados do formulário para salvar o novo usuário no banco de dados: public ActionResult New(UserModel model)

Recupera o objeto de classe preenchido na visualização Novo UserModel, verifica a correção dos dados (de acordo com os requisitos descritos em UserClasse LanguageClass), cria o objeto de repositório UsersRepositorye tenta adicionar uma nova linha ao banco de dados, chamando uma funçãoAddUser(model.User). Em seguida, ele retorna à página anterior (de onde a visualização foi chamada New) e uma mensagem é exibida indicando o sucesso da operação ou a falha.

3) Métodos Edit

Um método Editcom um parâmetro de entrada UserID(identificador do usuário) clica no nome do usuário na lista para abrir a visualização de edição dos dados do usuário: public ActionResult Edit(int UserID)

No método, um repositório é criado novamente UsersRepositorye uma função é chamada a partir dele FetchByID(UserID)para obter um objeto do tipo UserClass. Em caso de sucesso, um modelo é criado - um objeto de classe do objeto UserModelrecebido e uma lista de idiomas e passado para a exibição Editpara exibição.

Método com um parâmetro de entrada do tipo UserModele modelo de linha action:public ActionResult Edit(UserModel model, string action)

Um objeto de tipo é passado para esse método UserModele processa as ações quando os botões são clicados (processando eventos para o envio de dados do formulário) da exibição Edit. Para entender qual botão é pressionado, é usado o parâmetro de entrada action, indicado no nome das tags de tipo HTML input. Portanto, o método compara os valores desse parâmetro com os valores valuedesses parâmetros . Antes de verificar a validade do modelo, o valor é verificado Cancelpara executar ações para cancelar a edição do usuário. Esta opção é usada em vez de retornar ao histórico do navegador, que é usado na visualização Newe usa o armazenamento do endereço da página da qual a visualização foi passadaEdit. Para fazer isso, o controlador demonstrou a tecnologia para usar seu atributo ActionFilter: a classe ReferrerHoldAttributeno arquivo ReferrerHoldAttribute.cs (diretório HtmlAttribute ):

 public class ReferrerHoldAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var referrer = filterContext.RequestContext.HttpContext.Request.UrlReferrer; if (referrer != null) filterContext.RouteData.Values.Add("referrer", referrer); base.OnActionExecuting(filterContext); } } 

É usado para armazenar informações sobre a página em que você precisa retornar depois de clicar no botão " Novo usuário " ou em um link para alterar o usuário: TempData["referrer"] = ControllerContext.RouteData.Values["referrer"];

Precisamos dela para que cada vez que não escrevemos o mesmo código em métodos diferentes. Seria possível escrever uma função separada no controlador, mas se vários controladores forem usados, é mais prático usar um tipo de atributo especial.

Posteriormente, no corpo do método que processa as ações das visualizações Newe Edit, as informações armazenadas são extraídas e usadas para redirecionar de volta à página de onde essas visualizações foram chamadas: if (TempData["referrer"] != null) return Redirect(TempData["referrer"].ToString());

Para determinar se o usuário precisa ser salvo ou excluído, o parâmetro é comparado actioncom Salvar eRemova, respectivamente, e as UsersRepositoryfunções ChangeUser(model.User)e são chamadas do repositório RemoveUser(model.User). Depois, retornamos à página anterior (de onde veio a chamada de visualização Edit) e uma mensagem é exibida indicando o sucesso da operação ou a falha.

ADO.NET e MySQL - repositório


Aqui finalmente conseguimos trabalhar com o DBMS. Precisamos implementar as funções de adicionar um usuário a uma tabela Users, fazer alterações nos dados do usuário, remover um usuário da tabela, obter o usuário por ID, obter uma lista de usuários e obter uma lista de idiomas da tabela Languages.

Construções principalmente padrão são usadas para a classe MySQL.Data usando MySqlCommand:

 using (MySqlConnection connect = new MySqlConnection( )) { string sql = " "; using (MySqlCommand cmd = new MySqlCommand(sql, connect)) { cmd.Parameters.Add(" ",  ).Value =  ; connect.Open(); result = cmd.ExecuteNonQuery() >= 0; //        (INSERT, UPDATE, DELETE)   cmd.ExecuteScalar()         SELECT } } 

ou MySqlDataReaderpara ler as linhas da tabela como resultado de uma consulta:
 using (MySqlConnection connect = new MySqlConnection( )) { string sql = " "; using (MySqlDataReader dr = cmd.ExecuteReader()) { cmd.Parameters.Add(" ",  ).Value =  ; objConnect.Open(); while (dr.Read()) {//       } } } 

Crie o arquivo UserRepository.cs no diretório Models :

 public class UsersRepository { public bool AddUser(UserClass user) { user.UserID = AddUser(Name: user.Loginname, LanguageID: user.Language.LanguageID, Email: user.Email, SupporterTier: user.SupporterTier); return user.UserID > 0; } public int AddUser(string Name, int LanguageID, string Email, Supporter SupporterTier) { int ID = 0; using (MySqlConnection connect = new MySqlConnection(Base.strConnect)) { string sql = "INSERT INTO `Users` (`Loginname`, `LanguageID`, `Email`, `SupporterTier`) VALUES (@Loginname, @LanguageID, @Email, @SupporterTier)"; using (MySqlCommand cmd = new MySqlCommand(sql, connect)) { cmd.Parameters.Add("Loginname", MySqlDbType.String).Value = Name; cmd.Parameters.Add("LanguageID", MySqlDbType.Int32).Value = LanguageID; cmd.Parameters.Add("Email", MySqlDbType.String).Value = Email; cmd.Parameters.Add("SupporterTier", MySqlDbType.Int32).Value = SupporterTier; connect.Open(); if (cmd.ExecuteNonQuery() >= 0) { sql = "SELECT LAST_INSERT_ID() AS ID"; cmd.CommandText = sql; int.TryParse(cmd.ExecuteScalar().ToString(), out ID); } } } return ID; } public bool ChangeUser(UserClass user) { return ChangeUser(ID: user.UserID, Name: user.Loginname, LanguageID: user.Language.LanguageID, Email: user.Email, SupporterTier: user.SupporterTier); } public bool ChangeUser(int ID, string Name, int LanguageID, string Email, Supporter SupporterTier) { bool result = false; if (ID > 0) { using (MySqlConnection connect = new MySqlConnection(Base.strConnect)) { string sql = "UPDATE `Users` SET `Loginname`=@Loginname, `LanguageID`=@LanguageID, `Email`=@Email, `SupporterTier`=@SupporterTier WHERE UserID=@UserID"; using (MySqlCommand cmd = new MySqlCommand(sql, connect)) { cmd.Parameters.Add("UserID", MySqlDbType.Int32).Value = ID; cmd.Parameters.Add("Loginname", MySqlDbType.String).Value = Name; cmd.Parameters.Add("LanguageID", MySqlDbType.Int32).Value = LanguageID; cmd.Parameters.Add("Email", MySqlDbType.String).Value = Email; cmd.Parameters.Add("SupporterTier", MySqlDbType.Int32).Value = SupporterTier; connect.Open(); result = cmd.ExecuteNonQuery() >= 0; } } } return result; } public bool RemoveUser(UserClass user) { return RemoveUser(user.UserID); } public bool RemoveUser(int ID) { using (MySqlConnection connect = new MySqlConnection(Base.strConnect)) { string sql = "DELETE FROM `Users` WHERE `UserID`=@UserID"; using (MySqlCommand cmd = new MySqlCommand(sql, connect)) { cmd.Parameters.Add("UserID", MySqlDbType.Int32).Value = ID; connect.Open(); return cmd.ExecuteNonQuery() >= 0; } } } public UserClass FetchByID(int ID) { UserClass user = null; using (MySqlConnection objConnect = new MySqlConnection(Base.strConnect)) { string strSQL = "SELECT u.`UserID`, u.`Loginname`, l.`LanguageID`, l.`LanguageName`, u.`Email`, u.`LastLoginDate`, CAST(u.`SupporterTier` AS UNSIGNED) as `SupporterTier` FROM `Users` u LEFT JOIN `Languages` l ON l.LanguageID=u.LanguageID WHERE `UserID`=@UserID"; using (MySqlCommand cmd = new MySqlCommand(strSQL, objConnect)) { objConnect.Open(); int UserID = 0, LanguageID = 0; string Loginname = null, LanguageName= null, Email = String.Empty; Supporter SupporterTier = Supporter.None; DateTime? LastLoginDate = null; cmd.Parameters.Add("UserID", MySqlDbType.Int32).Value = ID; using (MySqlDataReader dr = cmd.ExecuteReader()) { if (dr.Read()) { UserID = dr.GetInt32("UserID"); Loginname = dr.GetString("Loginname").ToString(); LanguageID = dr.GetInt32("LanguageID"); LanguageName = dr.GetString("LanguageName").ToString(); if (!dr.IsDBNull(dr.GetOrdinal("Email"))) Email = dr.GetString("Email").ToString(); if (!dr.IsDBNull(dr.GetOrdinal("LastLoginDate"))) LastLoginDate = dr.GetDateTime("LastLoginDate"); if (!dr.IsDBNull(dr.GetOrdinal("SupporterTier"))) SupporterTier = (Supporter)dr.GetInt32("SupporterTier"); } LanguageClass language = null; if (LanguageID > 0) language = new LanguageClass(LanguageID: LanguageID, LanguageName: LanguageName); if (UserID > 0 && language != null && language.LanguageID > 0) user = new UserClass(UserID: UserID, Loginname: Loginname, Language: language, Email: Email, LastLoginDate: LastLoginDate, SupporterTier: (Supporter)SupporterTier); } } } return user; } //       ASP.NET WebForms,             ObjectDataSource      //     ,         " "         //public IEnumerable<DataRow> List() //{ // using (MySqlConnection objConnect = new MySqlConnection(Base.strConnect)) // { // string strSQL = "select * from users"; // using (MySqlCommand objCommand = new MySqlCommand(strSQL, objConnect)) // { // objConnect.Open(); // using (MySqlDataAdapter da = new MySqlDataAdapter(objCommand)) // { // DataTable dt = new DataTable(); // da.Fill(dt); // return dt.AsEnumerable(); // } // } // } //} public IList<UserClass> List(string sortOrder, System.Web.Helpers.SortDirection sortDir, int page, int pagesize, out int count) { List<UserClass> users = new List<UserClass>(); using (MySqlConnection objConnect = new MySqlConnection(Base.strConnect)) { //     string sort = " ORDER BY "; //   ,               (inject) // ,  , MySQL        //    ,         (       ) //        ,    if (sortOrder != null && sortOrder != String.Empty) { sort += "`" + sortOrder + "`"; if (sortDir == System.Web.Helpers.SortDirection.Descending) sort += " DESC"; sort += ","; } sort += "`UserID`"; //   //        ( ) string limit = ""; if (pagesize > 0) { int start = (page - 1) * pagesize; limit = string.Concat(" LIMIT ", start.ToString(), ", ", pagesize.ToString()); } string strSQL = "SELECT SQL_CALC_FOUND_ROWS u.`UserID`, u.`Loginname`, l.`LanguageID`, l.`LanguageName` as `Language`, u.`Email`, u.`LastLoginDate`, CAST(u.`SupporterTier` AS UNSIGNED) as `SupporterTier` FROM `Users` u LEFT JOIN `Languages` l ON l.LanguageID=u.LanguageID" + sort + limit; using (MySqlCommand cmd = new MySqlCommand(strSQL, objConnect)) { objConnect.Open(); cmd.Parameters.Add("page", MySqlDbType.Int32).Value = page; cmd.Parameters.Add("pagesize", MySqlDbType.Int32).Value = pagesize; using (MySqlDataReader dr = cmd.ExecuteReader()) { while (dr.Read()) { LanguageClass language = new LanguageClass(LanguageID: dr.GetInt32("LanguageID"), LanguageName: dr.GetString("Language").ToString()); users.Add(new UserClass( UserID: dr.GetInt32("UserID"), Loginname: dr.GetString("Loginname"), Language: language, Email: dr.IsDBNull(dr.GetOrdinal("Email")) ? String.Empty : dr.GetString("Email"), LastLoginDate: dr.IsDBNull(dr.GetOrdinal("LastLoginDate")) ? (DateTime?) null : dr.GetDateTime("LastLoginDate"), SupporterTier: dr.IsDBNull(dr.GetOrdinal("SupporterTier")) ? (Supporter) Supporter.None : (Supporter)dr.GetInt32("SupporterTier"))); } } } using (MySqlCommand cmdrows = new MySqlCommand("SELECT FOUND_ROWS()", objConnect)) { int.TryParse(cmdrows.ExecuteScalar().ToString(), out count); } } return users; } } 

Ele contém um método AddUserpara adicionar um usuário, atualizar dados do usuário ( ChangeUser), excluir ( RemoveUser), procurar usuário ( FetchByID) por identificador e o método mais interessante Listpara exibir uma lista paginada de usuários com classificação. Gostaria de comentar adicionalmente sobre a função List:

  1. Não vale a pena retornar a tabela ( DataTable) como tal - nesse caso, perderemos a capacidade de acessar os elementos da classe na visualização e seremos forçados a usar constantes de cadeia para se referir aos valores na linha da tabela. I.e. SQL-. UserClass .
  2. LIMIT SQL- SELECT . , MySQL , LIMIT . , , ORDER BY . , SQL-, SQL , , . , , : .
  3. SQL SELECT FOUND_ROWS() , , SELECT SQL_CALC_FOUND_ROWS LIMIT .

Todos os outros métodos são completamente comuns, consistentes com o cânon acima.

E o arquivo LanguageRepository.cs no diretório Models :

 public class LanguagesRepository { public IList<LanguageClass> List() { List<LanguageClass> languages = new List<LanguageClass>(); using (MySqlConnection objConnect = new MySqlConnection(Base.strConnect)) { string strSQL = "SELECT `LanguageID`, `LanguageName` as `Language` FROM `Languages` ORDER BY `LanguageName`"; using (MySqlCommand cmd = new MySqlCommand(strSQL, objConnect)) { objConnect.Open(); using (MySqlDataReader dr = cmd.ExecuteReader()) { while (dr.Read()) { LanguageClass language = new LanguageClass(LanguageID: dr.GetInt32("LanguageID"), LanguageName: dr.GetString("Language").ToString()); languages.Add(language); } } } } return languages; } } 

Ele possui apenas uma função para obter uma lista de idiomas do banco de dados, criando uma lista de elementos de classe LanguageClass.

Total


Isso é tudo - a tarefa está resolvida. É claro que muitos problemas não foram abordados: contêiner de injeção de dependência, teste de unidade, ferramentas para zombar, localização etc. etc.Algumas coisas podem ser feitas de maneira diferente ou simplesmente melhor. Mas o "bloco" acabou completo o suficiente para entender como o ASP.NET MVC funciona e que não é tão assustador trabalhar com ADO.NET e DBMSs que não são MS SQL.

PS E finalmente: você pode ver como este projeto funciona neste endereço . E aqui você pode baixar o projeto inteiro. Bem, para a pilha - um link para o meu blog .

Source: https://habr.com/ru/post/pt482346/


All Articles